A revenue leak is money your CRM says you earned but your billing system never actually collected. It's the deal marked "closed-won" that never turned into a subscription. The customer still paying Stripe every month who hasn't opened your product in six weeks. The marketing channel your CRM credits with a dozen wins that, when you check Stripe, generated a fraction of the revenue on paper. Clari and Gartner have both published estimates putting typical B2B revenue leakage at 3-8% of revenue — at $1M ARR, that's $30k-80k a year quietly falling through the cracks between systems nobody is cross-checking.
The frustrating part is that no single system is lying to you. HubSpot is telling the truth about what your sales team recorded. Stripe is telling the truth about what actually got charged. The leak only exists in the gap between them — and that gap is invisible unless you're actively looking for it.
The three most common leak patterns
After looking at this across enough RevOps setups, the leaks tend to cluster into three repeatable patterns.
Won but unpaid. A deal gets marked closed-won in HubSpot or Salesforce, but no corresponding Stripe subscription gets created within a reasonable window — say, 14 days. This is almost never fraud. It's usually a handoff failure: the contract got signed, the AE moved on to the next deal, and billing setup fell into the gap between sales and CS. Or onboarding got stuck waiting on a customer's procurement team and nobody flagged it as at-risk. Either way, the CRM says you closed a deal. The bank account says otherwise.
Silent churn. The Stripe subscription is active. The customer is being charged every month, and the payment is succeeding. But product usage has been at zero for 30+ days. This one is sneaky because every system involved looks healthy — Stripe shows a paying customer, and your CRM has no reason to flag anything since there's no explicit cancellation event. But a customer who's stopped logging in is already gone in every sense that matters. You're just collecting revenue on a relationship that's already over, and you'll find out for certain the day they cancel — by which point there was nothing you could have done. It's a retention problem wearing a billing status as a disguise.
Attributed but uncollected. Marketing points at a channel — a campaign, a content asset, an ad — and says "this generated $80k in won deals last quarter." The CRM backs that up: deals tagged with that source, marked won. But when you trace those same customers into Stripe, the actual collected revenue doesn't match. Maybe half of those "won" deals never converted to paying subscriptions (see leak #1). Maybe the CRM contact and the Stripe customer were never correctly linked in the first place, so the revenue exists but isn't attributed back to the source that earned credit for it. Either way, you're making channel-allocation decisions — where to spend the next marketing dollar — based on a number that isn't real.
Why this is hard to detect manually
None of these three patterns are visible from inside a single system. That's the core problem, and it's structural, not a matter of someone not looking hard enough.
Your data lives in at least three places: the CRM (HubSpot or Salesforce), the billing system (Stripe, or Chargebee, or whatever you're on), and your own product database that knows whether a customer is actually using what they're paying for. Each of those systems is internally consistent and each one, taken alone, looks fine. HubSpot has no idea whether a Stripe subscription exists. Stripe has no idea whether a customer opened your app this month. Your product database doesn't know what stage a deal was in before it became an account.
No single query, run against a single system, can surface any of these three leak patterns. You need a join across systems that were never designed to talk to each other, and most teams don't have that join running anywhere — they have a HubSpot dashboard, a Stripe dashboard, and a product analytics dashboard, none of which reference each other.
The identity resolution problem
This is the part most "how to fix revenue leaks" content skips entirely, and it's the actual hard part of the problem.
Before you can join anything, you need to know that customer_123 in Stripe, contact_456 in HubSpot, and user_789 in your application database all refer to the same human being. That sounds trivial. It isn't.
Stripe customers are often created by whoever set up billing, using whatever email was on the invoice — which might be a billing contact, not the actual buyer. HubSpot contacts get created from form fills, chat widgets, and manual entry, often with different email addresses for the same person (a work email for the original lead, a personal email if they filled out a demo form from home). Your product's user table has its own signup email, which may or may not match either of the above, especially if the account was provisioned by someone other than the end user.
Matching these requires real work: normalizing email addresses (case, aliasing, plus-addressing), matching on company domain when emails don't line up exactly, and — realistically — maintaining a manual mapping table for the accounts where automated matching fails. Skip this step, or do it sloppily, and every leak report downstream is garbage. You'll get false positives (flagging a "won but unpaid" deal that's actually paid, just under a different email) and false negatives (missing a real leak because the join silently failed to match two records that are actually the same customer). Identity resolution isn't a nice-to-have preprocessing step — it's the foundation the entire detection system sits on, and getting it wrong is the single most common reason revenue-leak dashboards get ignored after the first month.
How to build leak detection: three approaches
Manual spreadsheet. Export deals from HubSpot or Salesforce, export subscriptions and customers from Stripe, and VLOOKUP on email in a spreadsheet once a month. This genuinely works at under 50 customers, where you can eyeball mismatches by hand and the identity resolution problem is small enough to catch visually. It breaks down fast past that — the exports get stale the moment you finish them, nobody remembers to run it consistently, and VLOOKUP on raw emails silently fails on every alias or typo mismatch without telling you it failed.
Custom SQL + cron. If you're already landing HubSpot and Stripe data in a warehouse, you can write the join queries yourself — the query in the next section is a starting point. This is the right call if you have someone who owns data engineering and warehouse discipline. The catch is that "write it once" isn't the real cost; the real cost is maintaining it forever. Identity resolution logic needs updating as new edge cases show up, the queries need to run on a schedule somewhere, and someone has to own turning "here's a stale CSV" into "here's an alert that fires in Slack when a leak appears."
Cross-source monitoring platform. This is what a tool like Fastero's revenue monitoring is built for: connect Stripe, HubSpot (or Salesforce), and your product database once, let identity resolution run automatically across all three, and run preset leak checks — won-but-unpaid, silent-churn, source-attribution-gap — on a schedule with results delivered as a Slack digest instead of a dashboard you have to remember to open. The tradeoff versus the DIY options is upfront setup time in exchange for not having to be the person who maintains the join logic indefinitely.
The queries you need
Even if you end up on a monitoring platform, it helps to know what the underlying logic looks like. Here's the "won but unpaid" check in conceptual SQL, assuming you've already resolved identity down to a shared email or customer key:
-- Won but unpaid: deals closed >14 days ago with no matching Stripe subscription
SELECT deals.name, deals.amount, deals.close_date
FROM crm_deals deals
LEFT JOIN stripe_subscriptions subs
ON deals.contact_email = subs.customer_email
WHERE deals.stage = 'closed-won'
AND deals.close_date < NOW() - INTERVAL '14 days'
AND subs.id IS NULL;Silent churn follows the same shape, but joins Stripe against your product database instead of your CRM:
-- Silent churn: active paying subscription, no product usage in 30 days
SELECT subs.customer_email, subs.plan, subs.current_period_end
FROM stripe_subscriptions subs
LEFT JOIN product_events events
ON subs.customer_email = events.user_email
AND events.event_time > NOW() - INTERVAL '30 days'
WHERE subs.status = 'active'
AND events.user_email IS NULL;And source attribution gaps join CRM source data against actual collected revenue rather than deal amount:
-- Attributed but uncollected: CRM source credited with wins that never generated Stripe revenue
SELECT deals.source, COUNT(*) AS won_deals, SUM(deals.amount) AS crm_credited_revenue,
COALESCE(SUM(subs.amount_collected), 0) AS actual_collected_revenue
FROM crm_deals deals
LEFT JOIN stripe_subscriptions subs
ON deals.contact_email = subs.customer_email
WHERE deals.stage = 'closed-won'
GROUP BY deals.source
HAVING SUM(deals.amount) > COALESCE(SUM(subs.amount_collected), 0) * 1.2;These are simplified — production versions need the identity resolution layer sitting in front of the contact_email = customer_email join, since that naive equality is exactly where false negatives creep in.
What to do when you find a leak
Detection without a response is just a more expensive way to feel bad about the same problem. Each leak pattern maps to a specific, fast action:
- Won but unpaid → auto-create a task for CS or billing ops to follow up within 48 hours. The longer this sits, the more likely the deal quietly dies without anyone officially cancelling it.
- Silent churn → trigger a re-engagement email or a proactive check-in call before the cancellation happens, not after. This is the one pattern where early detection genuinely changes the outcome.
- Source attribution gap → audit your UTM tracking and the point where CRM contacts get created from marketing sources. Usually the gap traces back to a form or ad landing page that isn't passing attribution data cleanly into the CRM.
The point of building this monitoring isn't a monthly report — it's catching each of these within days instead of finding out at renewal time, or worse, at the board meeting where someone asks why attributed pipeline and collected revenue don't match.
The bottom line
Revenue leaks aren't a data quality problem inside any one system — they're what happens in the space between systems that were never built to reconcile with each other. Fixing this requires connecting CRM, billing, and product data, solving identity resolution honestly instead of hoping emails match, and running the same handful of join queries on a schedule instead of once a quarter when someone gets nervous.
If you want the pattern-matching without owning the pipeline yourself, see how Fastero handles revenue operations monitoring, HubSpot pipeline monitoring, and Stripe payment monitoring — or compare it against other options in our breakdown of the best revenue intelligence platforms.
Want to find out how much revenue is leaking between your CRM and billing right now? Start a free trial—connect Stripe and HubSpot, and get your first leak report before you finish your coffee.
Deep dives on specific leak patterns:
- How to Reconcile Stripe Revenue with Your CRM — building reconciliation from scratch
- How to Find Won-but-Unpaid Deals in HubSpot — the most common leak pattern
- Automate Won-but-Unpaid Deal Detection — HubSpot workflows + code
- 12 SQL Queries for CRM-to-Billing Reconciliation — copy-paste detection queries
- Detect Silent Churn: Paying Customers Who Stopped Using — zombie subscription detection
- Failed Stripe Payments Detection Guide — involuntary churn recovery
- Revenue Leakage in SaaS: 7 Causes and Fixes — the complete taxonomy
- Best Revenue Leak Detection Tools 2026 — 8 tools compared
- How to Sync Stripe to HubSpot with Python — complete integration tutorial
- How to Calculate Real MRR from the Stripe API — production-ready Python
Related: Revenue monitoring | Revenue ops monitoring | Best revenue intelligence platforms

