Build a Revenue Operations Dashboard with Claude Code [2026]
Your CRO asks: "Are we going to hit the number this month?"
You spend 4 hours pulling data from HubSpot, cross-referencing with finance, adjusting for pipeline weighting, and building a slide deck. By the time you present it, the data is 3 days old.
This is RevOps in 2025. It doesn't have to be RevOps in 2026.
With Claude Code, you can build a real-time revenue dashboard that:
- Pulls live data from CRM, marketing, and finance systems
- Automatically weights pipeline by historical close rates
- Surfaces risks before they become surprises
- Updates continuously, not monthly
Here's exactly how to build it.

Why Your Current Dashboards Fail
Problem 1: Data silos Pipeline is in HubSpot. Bookings are in the finance system. Marketing attribution is in Marketo. Usage data is in the product. Getting a complete picture requires manual assembly.
Problem 2: Stale data Weekly pipeline reviews use data that's already a week old. Monthly board decks are historical artifacts by the time they're presented.
Problem 3: No intelligence Dashboards show what happened, not what's likely to happen. They can't answer "should I be worried about this deal?"
Problem 4: Too many dashboards HubSpot has reports. Tableau has dashboards. Looker has boards. Nobody knows which one is "the truth."
The RevOps Dashboard Architecture
Here's what we're building:
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ HubSpot │ │ Stripe │ │ Marketo │
│ (CRM) │ │ (Revenue) │ │ (Marketing) │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
▼ ▼ ▼
┌────────────────────────────────────────────────────┐
│ Data Normalization Layer │
│ (Claude Code - ETL + Enrichment) │
└───────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Analytics Engine │
│ - Pipeline weighting │
│ - Forecasting models │
│ - Risk scoring │
│ - Attribution analysis │
└───────────────────────┬────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────┐
│ Executive Dashboard │
│ - Real-time metrics │
│ - Drill-down capability │
│ - AI-generated insights │
└────────────────────────────────────────────────────┘
Step 1: Data Integration with Claude Code
Let Claude Code build your data pipeline:
claude "Create a TypeScript module that:
1. Pulls deal data from HubSpot (all deals, all stages, with history)
2. Pulls revenue data from Stripe (MRR, ARR, churn, expansion)
3. Pulls campaign data from HubSpot Marketing (attribution, source)
4. Normalizes dates and amounts to consistent formats
5. Handles pagination and rate limiting
6. Stores in PostgreSQL with proper indexing
Use environment variables for API keys. Include comprehensive error handling."
Claude's 200K context window means you can provide full API documentation and get back production-ready code—not snippets that need assembly.
Sample Data Normalization
// data-pipeline.ts - Claude Code generated
interface UnifiedDeal {
id: string;
name: string;
company: string;
amount: number;
currency: 'USD';
stage: string;
stageHistory: StageChange[];
probability: number;
owner: User;
source: MarketingSource;
closeDate: Date;
createdAt: Date;
daysInStage: number;
lastActivity: Date;
contacts: Contact[];
}
const normalizeDeal = (hubspotDeal: HubSpotDeal): UnifiedDeal => {
return {
id: hubspotDeal.id,
name: hubspotDeal.properties.dealname,
company: hubspotDeal.associations?.companies?.[0]?.name || 'Unknown',
amount: normalizeAmount(
hubspotDeal.properties.amount,
hubspotDeal.properties.deal_currency_code
),
currency: 'USD',
stage: mapStage(hubspotDeal.properties.dealstage),
stageHistory: parseStageHistory(hubspotDeal),
probability: calculateProbability(hubspotDeal),
owner: await getOwner(hubspotDeal.properties.hubspot_owner_id),
source: await getAttribution(hubspotDeal),
closeDate: new Date(hubspotDeal.properties.closedate),
createdAt: new Date(hubspotDeal.properties.createdate),
daysInStage: calculateDaysInStage(hubspotDeal),
lastActivity: await getLastActivity(hubspotDeal.id),
contacts: await getContacts(hubspotDeal.id)
};
};
Step 2: Intelligent Pipeline Weighting
Raw pipeline is a fantasy number. Weighted pipeline predicts reality:
// pipeline-weighting.ts
interface WeightingModel {
byStage: Record<string, number>; // Historical close rates by stage
byAgeInStage: (stage: string, days: number) => number; // Decay factor
bySource: Record<string, number>; // Source quality multiplier
byDealSize: (amount: number) => number; // Large deal discount
}
const buildWeightingModel = async (): Promise<WeightingModel> => {
// Analyze last 12 months of closed deals
const closedWon = await getClosedWonDeals('12m');
const closedLost = await getClosedLostDeals('12m');
// Calculate actual close rates by stage
const byStage = calculateStageConversion(closedWon, closedLost);
// Example result:
// { 'Discovery': 0.12, 'Demo': 0.34, 'Proposal': 0.62, 'Negotiation': 0.78 }
// Calculate age decay
const byAgeInStage = buildDecayFunction(closedWon, closedLost);
// Example: Deals 2x average time in stage close at 40% the rate
// Calculate source quality
const bySource = calculateSourceQuality(closedWon);
// Example: { 'Inbound': 1.2, 'Outbound': 0.8, 'Partner': 1.1, 'Event': 0.7 }
// Calculate deal size impact
const byDealSize = buildSizeFunction(closedWon, closedLost);
// Example: Deals >$100K close at 70% the rate of average deals
return { byStage, byAgeInStage, bySource, byDealSize };
};
const weightDeal = (deal: UnifiedDeal, model: WeightingModel): number => {
const baseWeight = model.byStage[deal.stage] || 0.1;
const ageMultiplier = model.byAgeInStage(deal.stage, deal.daysInStage);
const sourceMultiplier = model.bySource[deal.source.channel] || 1;
const sizeMultiplier = model.byDealSize(deal.amount);
return deal.amount * baseWeight * ageMultiplier * sourceMultiplier * sizeMultiplier;
};
Step 3: Risk Scoring
Identify deals that need attention before they slip:
// risk-scoring.ts
interface DealRisk {
dealId: string;
score: number; // 0-100, higher = more risk
factors: RiskFactor[];
recommendation: string;
}
const calculateDealRisk = async (deal: UnifiedDeal): Promise<DealRisk> => {
const factors: RiskFactor[] = [];
let score = 0;
// Time in stage risk
const avgTimeInStage = await getAverageTimeInStage(deal.stage);
if (deal.daysInStage > avgTimeInStage * 1.5) {
score += 25;
factors.push({
type: 'stale',
description: `${deal.daysInStage} days in ${deal.stage} (avg: ${avgTimeInStage})`,
severity: 'high'
});
}
// Activity recency risk
const daysSinceActivity = daysBetween(deal.lastActivity, new Date());
if (daysSinceActivity > 14) {
score += 20;
factors.push({
type: 'inactive',
description: `No activity in ${daysSinceActivity} days`,
severity: 'medium'
});
}
// Close date risk
const daysToClose = daysBetween(new Date(), deal.closeDate);
if (daysToClose < 14 && deal.stage !== 'Negotiation') {
score += 30;
factors.push({
type: 'unrealistic_date',
description: `Closing in ${daysToClose} days but still in ${deal.stage}`,
severity: 'high'
});
}
// Champion identified
const hasChampion = deal.contacts.some(c => c.role === 'Champion');
if (!hasChampion && deal.amount > 50000) {
score += 15;
factors.push({
type: 'no_champion',
description: 'No champion identified on $50K+ deal',
severity: 'medium'
});
}
// Generate recommendation
const recommendation = await generateRecommendation(deal, factors);
return { dealId: deal.id, score, factors, recommendation };
};
Step 4: The Dashboard
Build a clean, executive-ready interface:
// dashboard.ts
interface ExecutiveDashboard {
// The Numbers
currentMonth: {
target: number;
closed: number;
committed: number; // High-confidence pipeline
bestCase: number; // Weighted pipeline
gap: number;
};
// Pipeline Health
pipeline: {
total: number;
weighted: number;
byStage: Record<string, { count: number; value: number; weighted: number }>;
created: { thisMonth: number; lastMonth: number; change: number };
velocity: { avgDaysToClose: number; trend: 'faster' | 'slower' | 'stable' };
};
// Risks & Opportunities
risks: DealRisk[];
stuckDeals: UnifiedDeal[];
pushedDeals: UnifiedDeal[]; // Close date moved out
bigMoves: UnifiedDeal[]; // Stage changed this week
// Trends
trends: {
winRate: { current: number; previous: number; change: number };
avgDealSize: { current: number; previous: number; change: number };
salesCycle: { current: number; previous: number; change: number };
};
// AI Insights
insights: string[];
}
const buildDashboard = async (): Promise<ExecutiveDashboard> => {
const deals = await getAllDeals();
const model = await buildWeightingModel();
// Calculate metrics
const currentMonth = await calculateMonthMetrics(deals, model);
const pipeline = await analyzePipeline(deals, model);
const risks = await Promise.all(
deals
.filter(d => d.stage !== 'Closed Won' && d.stage !== 'Closed Lost')
.map(d => calculateDealRisk(d))
);
// Generate AI insights
const insights = await generateInsights({
currentMonth,
pipeline,
risks: risks.filter(r => r.score > 50)
});
return {
currentMonth,
pipeline,
risks: risks.sort((a, b) => b.score - a.score).slice(0, 10),
stuckDeals: deals.filter(d => isStuck(d)).slice(0, 5),
pushedDeals: await getPushedDeals('7d'),
bigMoves: await getBigMoves('7d'),
trends: await calculateTrends(),
insights
};
};

Step 5: AI-Generated Insights
The dashboard doesn't just show data—it explains it:
// insights.ts
const generateInsights = async (data: DashboardData): Promise<string[]> => {
const prompt = `
You are a RevOps analyst. Based on this data, provide 3-5 key insights
that would be valuable for a CRO in their Monday morning review.
Be specific. Use numbers. Flag concerns. Highlight wins.
Data:
- Target: $${data.currentMonth.target.toLocaleString()}
- Closed: $${data.currentMonth.closed.toLocaleString()} (${(data.currentMonth.closed / data.currentMonth.target * 100).toFixed(0)}% of target)
- Committed: $${data.currentMonth.committed.toLocaleString()}
- High-risk deals: ${data.risks.filter(r => r.score > 70).length}
- Deals pushed this week: ${data.pushedDeals.length}
- Win rate trend: ${data.trends.winRate.change > 0 ? 'up' : 'down'} ${Math.abs(data.trends.winRate.change)}%
Format as bullet points, no headers.
`;
const response = await claude.complete(prompt);
return response.split('\n').filter(line => line.startsWith('-') || line.startsWith('•'));
};
Example output:
• On track but tight: At 68% of target with 12 days left. Need $127K from committed pipeline that's currently weighted at $143K. No buffer.
• Acme Corp is the swing deal: $85K deal in Negotiation, but 23 days in stage (avg: 11). Risk score 78. Recommend: exec-to-exec call before Friday.
• Win rate improving: 34% this month vs 28% last month. Driven by better qualification—MQL rejection rate up 15%.
• Pipeline generation concern: Only $340K created this month vs $520K target. Marketing sourced leads down 22%. Check campaign performance.
• 3 deals pushed close dates this week totaling $124K. Pattern: all had unrealistic close dates set during initial discovery. Consider discovery checklist update.
Automated Distribution
Push insights where people actually look:
// distribution.ts
// Monday morning CRO briefing
const mondayBriefing = async () => {
const dashboard = await buildDashboard();
await slack.postMessage({
channel: '#revenue-leadership',
blocks: [
{
type: 'header',
text: { type: 'plain_text', text: '📊 Monday Revenue Brief' }
},
{
type: 'section',
fields: [
{ type: 'mrkdwn', text: `*Closed:* $${dashboard.currentMonth.closed.toLocaleString()}` },
{ type: 'mrkdwn', text: `*Target:* $${dashboard.currentMonth.target.toLocaleString()}` },
{ type: 'mrkdwn', text: `*Committed:* $${dashboard.currentMonth.committed.toLocaleString()}` },
{ type: 'mrkdwn', text: `*Gap:* $${dashboard.currentMonth.gap.toLocaleString()}` }
]
},
{
type: 'section',
text: {
type: 'mrkdwn',
text: `*AI Insights:*\n${dashboard.insights.join('\n')}`
}
}
]
});
};
// Daily risk alerts
const dailyRiskAlerts = async () => {
const dashboard = await buildDashboard();
const criticalRisks = dashboard.risks.filter(r => r.score > 70);
if (criticalRisks.length > 0) {
for (const risk of criticalRisks) {
const deal = await getDeal(risk.dealId);
await slack.postMessage({
channel: deal.owner.slackId,
text: `⚠️ Risk alert on *${deal.name}* ($${deal.amount.toLocaleString()})\n\n${risk.factors.map(f => `• ${f.description}`).join('\n')}\n\n*Recommendation:* ${risk.recommendation}`
});
}
}
};
Implementation Timeline
Week 1: Data Layer
- Set up PostgreSQL database
- Build HubSpot integration
- Build Stripe integration (if applicable)
- Create normalization layer
Week 2: Analytics
- Implement weighting model from historical data
- Build risk scoring
- Create trend calculations
Week 3: Dashboard
- Build dashboard API
- Create frontend (or use existing BI tool)
- Integrate AI insights
Week 4: Distribution
- Set up Slack integrations
- Configure automated briefings
- Train team on using the dashboard
The ROI
| Metric | Before | After |
|---|---|---|
| Time to answer "how are we doing?" | 4 hours | 10 seconds |
| Pipeline accuracy | 60% | 85% |
| Deals slipping unnoticed | 5-7/month | 0-1/month |
| Forecast variance | ±25% | ±10% |
What's Next?
Once your RevOps dashboard is running:
- Add product usage data — See which customers are actually using what they bought
- Build scenario modeling — "What if we slip these 3 deals?"
- Connect to capacity planning — Does pipeline support hiring plan?
- Enable board reporting — Auto-generate monthly investor updates
Ready to stop guessing and start knowing? Book a demo to see how MarketBetter combines real-time intelligence with AI-powered workflows.
Related reading:
