Why your analytics and Stripe disagree, and how to reconcile them
Updated 2026-11-02
You open your analytics and it says $42,000 in revenue last month. You open Stripe and it says $51,300. Neither one has a bug that you can see. So which is right, and why don't they match?
Stripe is almost always right about money. Your analytics is an estimate of behavior that often includes a copy of revenue. Those two systems are built differently, and that's why they don't match.
This post explains the usual causes and gives you a way to reconcile the two.
Two systems measuring different things
Stripe records money. Each charge, refund and dispute is a financial event that's processed on its servers.
Most analytics tools record behavior in a browser. A script fires when something happens on a page. If a tool shows revenue, it's usually because a page sent an event like "purchase, $99".
That difference explains most of the gap. One system watches money move. The other watches a browser say that money moved.
Why analytics shows less than Stripe
Ad blockers. Some visitors block analytics scripts. Their purchases exist in Stripe and never appear in your analytics.
Thank-you pages that don't load. If revenue is sent from a confirmation page, it depends on the buyer reaching it. Someone closes the tab during a redirect, and that sale is lost to analytics.
Renewals and subscriptions. This is usually the largest gap for SaaS. A renewal happens in Stripe with no browser involved. There's no page view, so there's no event. Upgrades made by your team or through invoices can be missed the same way.
Payments outside your site. Stripe invoices, payment links and manual charges often skip your site altogether.
Failed event sends. A slow network or a script error can drop events.
Why analytics shows more than Stripe
Refunds. Your analytics recorded the sale. The refund came later and never reached it. Stripe nets it out, while analytics keeps the original figure.
Disputes. The same goes for chargebacks. Money can leave your account weeks after the sale.
Duplicate events. A buyer refreshes the thank-you page and the purchase fires twice.
Failed payments. If an event fires when someone clicks Pay, it may count a card that was declined.
Test traffic. Your team's test purchases can show up in analytics if they aren't filtered.
Why the same totals can still look different
Even with perfect data, these can split the numbers:
Time zones. Stripe and your analytics may cut days at different hours. Sales near midnight fall on different dates. At the edge of a month, that changes both totals.
Currency. Stripe stores each payment in its own currency. An analytics tool may convert it at a different rate, or add up figures in different currencies.
Timing. A payment can be charged on the 31st and refunded on the 2nd. Each system may place those in different months.
Definitions. Stripe has several totals: gross volume, net volume and payouts. Payouts are after fees and arrive days later. If you compare analytics with your bank deposits, you'll be off by fees and timing. Use the same definition on both sides.
Taxes and shipping. One system may include tax while the other doesn't.
Attribution windows. An attribution report can show less than total revenue because some payments had no visit within the window. That's expected if the tool reports them as unattributed.
How to reconcile analytics and Stripe
Pick one month that has closed. Then work through these steps.
Step 1: Decide which number is the truth
For revenue, use Stripe. Analytics should explain revenue. It shouldn't define it.
Step 2: Match your definition
Pick a single definition and use it on both sides. A good one is payments minus refunds and disputed money, before Stripe fees, in one currency.
Step 3: Match the dates
Set both reports to the same time zone and the same date range.
Step 4: Split by payment type
Separate new purchases from renewals. Analytics tools that rely on browser events will only have the first group. Compare new purchases on their own.
Step 5: Build a bridge table
List each cause and its amount, so you can explain the gap. Here's a made-up example:
| Item | Amount |
|---|---|
| Stripe net revenue | $51,300 |
| Renewals with no browser event | -$8,900 |
| Invoices paid outside the site | -$2,400 |
| Purchases blocked or lost in analytics | -$1,700 |
| Refunds still counted in analytics | +$1,500 |
| Duplicate purchase events | +$400 |
| Timing at month edges | +$1,800 |
| Other, unexplained | $0 |
| Analytics total | $42,000 |
A small unexplained amount is normal. A big one means something is broken.
Step 6: Sample a few sales
Pick ten Stripe payments and find each one in your analytics. Then pick ten analytics purchases and find them in Stripe. Patterns show up quickly. You might find that one checkout flow never fires its event.
Step 7: Fix the source
If you're sending revenue from the browser, consider moving to server-side events or Stripe webhooks. These don't depend on a page loading.
What good looks like
For traffic, tools can legitimately differ. Ad blockers and bot filters make visitor counts an estimate.
Revenue is different. It should be exact, because the data exists in Stripe. If your attribution reports don't tie to your billing, you should know by how much, and why. Our guide to marketing attribution explains why this matters when you're setting budgets.
How Headstream handles this
Headstream takes revenue straight from Stripe, so it doesn't rely on a thank-you page. You connect with read-only access, which is in beta, and it imports your history. Renewals, invoices and payments outside your site come in with everything else.
Charges, refunds and disputes each become an entry in an append-only ledger. Revenue is payments minus refunds and disputed money. A disputed payment goes in as a negative entry when Stripe takes the money, and a positive one if it comes back. Stripe fees aren't subtracted, so compare it with Stripe's gross activity minus refunds and disputes, and don't compare it with payouts. Payments in other currencies are reported separately.
Each hourly attribution run is checked against that ledger. A run publishes only if its credits equal the ledger. If they don't, the run fails and the last good one stays up. Refunds and disputes are credited back to the original sale. A payment with no visit in the lookback window stays unattributed, and that keeps the total honest.
You can check it yourself. The payment trail follows any payment from the Stripe event through the ledger and attribution run to each credited visit. Why this number pages show the raw rows, and the data quality page shows what was filtered. See our trust page and methodology.
Traffic numbers in Headstream are still estimates, and we label them that way. Blocked scripts stay invisible to us as well. If you're choosing a tool, read our marketing attribution software guide.
Questions
- Why doesn't Google Analytics revenue match Stripe?
- Browser-based revenue tracking misses blocked scripts, renewals and off-site payments, and it often doesn't subtract refunds. Definitions, time zones and currency can also differ.
- Which number should I trust, analytics or Stripe?
- Use Stripe for revenue. Use analytics to explain where that revenue came from.
- How close should analytics be to Stripe?
- If revenue comes from a direct Stripe connection, it should match exactly on the same definition. With browser events, expect a gap, and make sure you can explain it.