How to audit that every HubSpot deal has a matching Stripe invoice
Finance asks a simple question at close: does every closed-won deal have a real invoice behind it in Stripe™, and does every invoice Stripe collected show up as revenue in HubSpot? There's no button for this. Most teams answer it with a quarterly export, a spreadsheet lookup, and a fair amount of manual judgment about what counts as a match — and the gap between audits is where the real risk sits.
Why it happens
Nothing natively compares the two systems. HubSpot can list your closed-won deals. Stripe can list your invoices. Neither product ships a report that joins them, because from either system's point of view, the other one doesn't exist. Any reconciliation between the two has to be assembled deliberately — there's no setting to turn on.
Deletes and cancellations don't propagate. If a rep deletes a deal in HubSpot — by accident, as a duplicate cleanup, or because a customer churned — nothing notifies Stripe, and the subscription there keeps billing exactly as before. HubSpot's own documented behavior for its native accounting-sync tools is explicit that a delete on one side is not automatically mirrored to the other. Run this in reverse and it's worse: a subscription canceled in Stripe doesn't close the associated deal in HubSpot, so the CRM keeps showing revenue nobody is actually collecting anymore.
There's no reliable join key by default. The same problem that makes company association hard (see our piece on invoices and companies) shows up again here: matching a Stripe invoice to a HubSpot deal usually falls back to customer email or company domain, which breaks whenever the billing contact isn't the deal's primary contact, or the deal was created under a different name than the Stripe customer record uses.
A practical audit you can run today
1. Export both sides for the same period
From Stripe: Dashboard → Billing → Invoices (or the Invoices list API), with customer email, amount, currency, status and invoice ID. From HubSpot: the Deals index, filtered by close date and stage, exported with the associated Contact/Company email and deal amount.
2. Join on email, then fall back to domain for what doesn't match
Most rows will match cleanly on email. For what's left, try matching on domain, and expect to resolve a residual few by hand — this is normal, not a sign you're doing it wrong.
3. Compare in the same currency before trusting an amount mismatch
Stripe invoices carry their own currency; a HubSpot deal amount is typically entered in the portal's default currency. A raw numeric comparison between a Stripe invoice in one currency and a deal amount in another can look like a match and be wrong, or look like a mismatch and be correct. Convert both to one currency — using the rate recorded at the time of the transaction, not today's rate — before flagging anything as a genuine discrepancy.
4. Flag four categories of mismatch specifically
| Mismatch type | What it means |
|---|---|
| Stripe invoice, no matching closed-won deal | Revenue collected that nobody logged in the CRM |
| Closed-won deal, no matching paid Stripe invoice | CRM believes revenue exists that Stripe never actually collected |
| Both exist, amounts disagree | Partial payment, discount, or a later adjustment neither side reflects |
| Both exist, amounts matched at close but have since diverged | A subscription was upgraded, downgraded or prorated in Stripe after the deal amount was set, and the deal was never updated |
The fourth row is the easiest to miss, because it produces no export discrepancy on day one. A deal amount is usually stamped once at close and rarely revisited, while the Stripe subscription behind it can change every billing cycle.
5. Store the join key going forward, not just for this audit
Add a stripe_invoice_id (and stripe_customer_id) property to the Deal at creation time. Every future audit against a deal created this way becomes an exact-ID lookup instead of a fuzzy email match — this alone removes most of the manual judgment calls in step 2.
6. Check for the deletion case specifically
Cross-reference deals your team knows were deleted or marked lost against Stripe's still-active subscriptions. A canceled-in-HubSpot, still-billing-in-Stripe mismatch is comparatively rare but expensive when it happens — a customer is being charged for a relationship the CRM no longer tracks, which nobody catches until the customer complains or someone happens to check.
7. Keep the last audit's output, not just the current one
Save each period's matched/unmatched spreadsheet. Comparing this quarter's gap list to last quarter's tells you whether the same kind of mismatch keeps recurring — which usually points at a process problem (a particular deal source, a particular rep, a particular payment method) rather than a one-off error.
8. Decide a cadence and keep it
Quarterly is the minimum that catches serious drift before it compounds across several close cycles; monthly is considerably safer once deal volume or average contract value is high enough that a quarter of undetected mismatch would be expensive to unwind. The audit only works if it actually repeats — the value is in the trend across cycles, not any single run.
Where this breaks down
It's a snapshot, not a process. The moment you finish the export, new invoices and new deals are already outside the audit. Most teams can only sustain this quarterly, which means drift accumulates for months between checks by default.
Email and domain joins mis-match the same way they do everywhere else in Stripe-to-HubSpot work — free email providers, accounts-payable inboxes on a different domain, a reseller or parent company paying centrally for several accounts.
A manual audit finds the symptom, not the cause. You'll see that a deal and an invoice don't match, but not necessarily whether that's because of a canceled subscription, a failed sync, a deleted deal, or a rep who never created the deal in the first place — so the same category of gap tends to reappear every quarter with a new set of records.
By construction, you only find out at the next audit cycle. For most teams that's month-end or quarter-end — precisely when there's the least slack to investigate a discrepancy properly, and the most pressure to just write it off and move on.
Currency conversion timing makes even a careful manual audit harder than it looks. The "correct" rate depends on which date you anchor to — invoice date, payment date, or the day you happen to run the audit — and different reasonable choices produce different amounts that all look defensible. A process that always converts at today's rate will manufacture false mismatches that have nothing to do with an actual data problem, and burn time chasing them.
What Trueness does about it
Trueness's free Drift Report runs this exact comparison every day instead of every quarter, and shows you every mismatch — a Stripe invoice with no matching closed-won deal, a closed-won deal with no matching paid invoice — priced in dollars, so you can check each one against Stripe yourself. Recording how each mismatch was resolved is a paid-tier feature on our roadmap, not built yet; today, the report gives month-end a list to start from instead of a spreadsheet rebuilt from nothing.