Clarvivo blog
Why your analytics conversions never match Stripe
Your dashboard says 61 conversions. Stripe says 47. Here is where the other 14 go, and which number to trust.
- attribution
- revenue
- analytics
Every growth team eventually runs the same reconciliation and gets the same result. Analytics reports 61 conversions last month. Stripe reports 47 new subscriptions. Nobody can explain the gap, so the meeting ends with everyone agreeing to "look into the tracking" and nothing changes.
The gap is real, it is rarely small, and it is not one bug. It is four separate leaks that happen to drain into the same hole.
1. The conversion event fires before the money arrives
This is the big one and it is almost always mis-modelled. A signup event fires when someone submits a form. A payment happens when a card is charged — which for a trial-led product is 14 days later, and for a sales-led product might be six weeks later after two calls and a procurement review.
Analytics counts the first thing. Finance counts the second. They were never measuring the same event, so they were never going to agree.
The tell: your conversion count is consistently higher than your payment count, and the ratio is roughly your trial-to-paid rate.
2. Blocked scripts
Analytics scripts are blocked for a meaningful slice of traffic, and B2B SaaS skews high — the people evaluating your product are disproportionately technical and disproportionately running blockers. Measure it on your own site rather than trusting a benchmark: compare signups recorded in your database against signups your analytics saw, over the same window.
A blocked visitor can still sign up and still pay. They just do it invisibly. This leak runs the other way: it makes analytics undercount relative to Stripe, which is why the two errors sometimes partially cancel and leave a gap smaller than either underlying problem.
3. Consent-gated tracking
If your tracker only loads after someone accepts a cookie banner, then everyone who declined, ignored, or bounced before deciding is invisible to it — but entirely visible to Stripe if they later buy.
Check what proportion of your revenue is filed under "Direct" or "Offline". That bucket is where consent-gated and blocked visitors go to die. If it is over a quarter of closed-won, attribution is not telling you much.
4. Refunds, failed payments, and expansion
Analytics records a conversion once and never revisits it. The customer who churned in month two is still a conversion. The failed renewal is still a conversion. The account that grew from 12 seats to 45 is still exactly one conversion, worth the same as the one that shrank to three.
For per-seat pricing this is the most expensive error of the four, because it means your channel rankings are built on a number that stops updating the moment someone pays.
Which number should you trust?
Neither, in isolation. They answer different questions:
| Question | Source |
|---|---|
| How many people showed intent? | Analytics |
| How much money actually arrived? | Payment processor |
| Which campaign earned that money? | Neither, on its own |
The third question is the one worth solving, and it is not solvable by making the first two agree. A payment webhook is server-to-server: it knows the amount and the customer and has no idea a marketing campaign ever existed. So revenue gets grouped by payment processor, and every dollar you earned reports the same useless answer — "Stripe".
Closing the loop
The fix is unglamorous. You carry one identifier across the gap.
- Assign each visitor a first-party ID on their first visit, and record the first and last campaign that brought them.
- When they convert, send that ID along with the signup — a hidden form field
into your CRM, or
client_reference_idon a Stripe Checkout session. - When the money actually arrives, post it back with the ID attached.
Now the payment row carries the campaign that earned it. Not a model, not a statistical guess at a multi-touch weighting — a join on a key. The number is either right or absent, which is a much better property than being confidently approximate.
Two things worth knowing before you build it:
Sign the revenue endpoint. An unauthenticated endpoint that accepts revenue figures is a number anyone who reads your JavaScript can inflate. If budget decisions depend on attributed revenue, that path needs authentication like any other write.
Same-device only. An identifier in browser storage cannot follow someone from their phone to their laptop. If a meaningful share of your funnel researches on mobile and buys on desktop, you need a login or a form submission as the stitch point, and you should know that before you trust the totals.
Clarvivo does this out of the box — first-party visitor IDs with no cookies, first- and last-touch attribution selectable at query time, and signature-verified revenue webhooks from Stripe, Razorpay, Paddle, Polar and LemonSqueezy. Posting your own revenue instead? The custom API supports HMAC request signing, so the figures in your dashboard are ones only your systems could have written. See how it works.