Two tabs open, side by side. Shopify's admin says the store did $84,200 last week. GA4 says $71,600. Nobody in the meeting can explain the $12,600 gap, so the safest-sounding answer wins: "GA4 always undercounts, that's normal." The meeting moves on.
That answer is right often enough to be dangerous. Ad blockers, consent declines, and GA4's own processing delay do cause GA4 to read lower than Shopify, and a lot of the time that explains most of the gap. But we have walked into reconciliations where GA4 read higher than Shopify, and the undercounting explanation would have sent the team looking in exactly the wrong direction while a duplicate tag kept inflating every report underneath it.
The direction of the gap changes the diagnosis. Before accepting "that's just how GA4 works" as the final answer, it is worth running the reconciliation that actually resolves a GA4 Shopify revenue mismatch and tells you which of the two platforms is closer to the truth, and why.
Why the two numbers were never going to match exactly
Shopify's revenue figure comes from its own database: an order exists the moment payment clears, full stop. GA4's revenue figure comes from a purchase event that has to survive the customer's browser, a network request, and GA4's own processing pipeline before it counts for anything. Those are structurally different systems, not two measurements of the same underlying record.
That gap has a normal range. Client-side GA4 tracking commonly runs 10 to 20 percent below Shopify's own order total, driven by three things that have nothing to do with configuration quality:
- Browser-side loss. Ad blockers, privacy browsers, and customers closing the confirmation tab before the pixel fires all prevent an event Shopify already recorded from ever reaching GA4.
- Consent declines. A visitor who declines cookie consent completes a real Shopify order with no corresponding GA4 hit, by design.
- Processing latency. GA4 explicitly does not treat its standard processing window as a guarantee, and Google's own data freshness documentation notes that some data can lag behind for hours, and occasionally longer, after Shopify has already closed the order.
Inside that 10 to 20 percent band, the gap is expected measurement loss, not a bug to chase. Outside that band, in either direction, something specific and fixable is usually the cause.
The reconciliation, in the order that actually isolates the cause
Running through causes in a list rarely tells you which one is actually responsible. Reconciling in this order does, because each step removes one variable before you look at the next.
1. Match the reporting window exactly
Confirm both platforms are set to the same timezone and the same date range before comparing anything else. Shopify reports in your store's configured timezone; GA4 reports in the property's configured timezone, and if those two settings disagree, orders placed near midnight land on different calendar days in each report and create a gap that has nothing to do with tracking.
2. Normalize for refunds
Shopify deducts a refund from net revenue the moment it processes, automatically. GA4 only removes that value if a refund event was explicitly sent for the refunded order; GA4's recommended events documentation covers the event and parameters required to do that. If refund events are not implemented, GA4 will always run higher than Shopify's net figure by roughly however much was refunded that period, and it will look like overcounting even though nothing is duplicated. Pull Shopify's own sales report, which separates gross sales from returns, and compare that gross figure to GA4 before assuming a real discrepancy exists.
3. Check currency handling
If the store sells in multiple currencies or Shopify Markets is active, confirm both platforms are converting at the same rate and reporting in the same base currency. A stale or mismatched exchange rate produces a gap that scales with international order volume, not with any tracking problem.
4. Open DebugView and watch one real order
With timezone, refunds, and currency ruled out, place a real order (or use Shopify's test mode). DebugView only shows a session once debug mode is switched on for it, either through the GA4 DebugView extension, a GTM preview session, or a debug_mode parameter on the hit, so turn that on before you check out, not after. Then count exactly how many purchase events fire and note the value attached to each. This single check is where we find the two failure modes that explain most unresolved gaps:
- Zero events fire. The purchase event never reaches GA4 at all, which points at a broken trigger, a blocked script, or a consent configuration issue, and explains an undercounting gap.
- Two events fire for one order. A duplicate tag, a native platform pixel app running its own copy of the same events alongside GTM, or a page refresh on the thank-you page re-firing the event all produce this, and it explains an overcounting gap that no amount of "GA4 always undercounts" reasoning will ever catch.
We have seen the second failure mode directly, though on a non-purchase event: a native platform pixel app re-ran the same tags in a sandboxed iframe alongside the site's own GTM container, sending two page_view hits to one GA4 property under two different visitor IDs for every page load. The same mechanism, an app-installed pixel duplicating a container's own tags, is exactly what to rule out on the purchase event specifically once DebugView is live on a real checkout.
5. Total the remaining gap and compare it to the normal range
Once timezone, refunds, and currency are normalized and DebugView confirms exactly one clean purchase event per order, whatever gap remains is the real browser-loss number. If it lands in the 10 to 20 percent range, that is expected and not worth engineering away. If it is smaller, larger, or reversed after all four checks, the cause is specific and still findable, not an inherent GA4 limitation.
What this actually costs when nobody checks
A wrong revenue number does not stay contained to one dashboard. Blended ROAS, marketing efficiency ratio, and every paid media decision built on "how much did we actually make" inherit whatever the underlying reconciliation gets wrong. In one reporting audit we ran, summed platform-attributed revenue exceeded actual store revenue by roughly 40 percent in the same window, traced back to the same order IDs appearing in more than one platform's conversion export, double-counted rather than double-earned. The store had been making spend decisions off a number that was structurally inflated for months.
That is the pattern worth internalizing: a gap you can name and explain is manageable. A gap nobody has actually diagnosed gets treated as ambient noise and quietly shapes every budget decision built on top of it.
When to run this yourself and when to get help
The five-step reconciliation above is a same-day check for a technical marketer or developer comfortable in GA4's DebugView and Shopify's admin. If your gap sits in the normal range and you can name the cause after step 2 or 3, there is nothing more to fix.
If the gap is outside that range, moves inconsistently week to week, or you finish all five steps and still cannot explain it, that usually means the root cause lives in a part of the setup this checklist assumes is correct, like the underlying GTM container or the Meta Conversions API deduplication feeding a separate ad platform number entirely.
That is the case our ecommerce reporting audit is built for: a fixed-price engagement that reconciles GA4, your ad platforms, and Shopify's own numbers against each other and tells you which one to trust, with a sample report at /services/audits/samples/reporting showing what the findings look like. Our post on what a reporting audit checks before you trust any dashboard walks through the broader version of this same problem.
If the mismatch traces back to the tracking layer itself rather than the reporting layer, our GA4 ecommerce tracking setup guide covers the datalayer-first build that prevents this exact class of duplicate-event problem from the start. And if you are not yet sure which layer the issue is actually in, an analytics audit is the fastest way to find out before committing to either.
The decision that actually matters here
Do not decide whether to act based on how big the gap looks. Decide based on which side of the reconciliation it falls on. A gap inside the normal range, after refunds, timezone, and currency are normalized, is expected measurement loss and safe to report with a caveat. A gap outside that range, in either direction, is a named, fixable problem sitting somewhere in the five steps above, and it is cheaper to find this week than to keep reporting around it for another quarter.

