GA4 does not match Shopify: normal gaps versus real bugs
GA4 and Shopify will never match exactly, because they measure different objects with different definitions, timezones and refund handling. Reconcile on purchase count first, matched at the order ID level. As a working rule, a few percent below Shopify is normal residue, ten percent or more is worth hunting, and GA4 above Shopify is always a bug.
Updated
The symptom: GA4 below Shopify
GA4 and the Shopify admin disagree. Sessions in GA4 sit below sessions in Shopify’s reports. GA4 revenue for the month does not equal total sales. Purchase count is under order count. You export both, line them up, and they refuse to reconcile.
The useful question is not how to make them match. They will not match. The question is whether your gap is the expected residue of two systems measuring different things, or evidence that collection is broken.
Why GA4 and Shopify never match
The causes fall into two groups.
Legitimate reasons the numbers will never be identical.
Timezone. GA4 reports on the property timezone, Shopify on the store timezone. If they differ, every day boundary shifts and orders near midnight land on different days.
Revenue definition. Shopify separates gross sales, discounts, returns, net sales, shipping, tax and total sales. GA4 reports whatever value your purchase event sends. A dataLayer sending the order total including tax and shipping will not match net sales. One sending the subtotal will not match total sales. Neither is wrong. They are different definitions.
Refunds. Shopify reduces sales when an order is refunded. GA4 does not, unless you send a refund event carrying the matching transaction_id. Without it, GA4 revenue drifts upward relative to Shopify over time.
Sessions are not the same object. Shopify counts sessions from its own storefront traffic records with its own timeout and bot filtering. GA4 counts sessions from the JavaScript tag with its own timeout, bot exclusion, and handling of campaign changes mid visit. Two definitions, two systems, two numbers.
Consent. Where a visitor denies analytics storage, GA4 does not receive ordinary hits. Shopify’s server side order record is unaffected. Any denial rate at all opens a gap.
Ad blockers, browser tracking prevention and dropped network requests. Client side measurement loses hits. Server side order recording does not.
Illegitimate reasons, which are bugs.
The purchase tag never runs on the checkout. Shopify’s newer checkout runs third party measurement through a sandboxed customer events layer rather than theme scripts, so a container installed in the theme will not execute on checkout or order confirmation unless it is deliberately wired through that layer. This is the most common reason a Shopify property under-reports purchases badly.
Missing or malformed transaction_id, so nothing can be reconciled or deduplicated.
Cross domain breakage. A hosted payment step on another domain starts a new session and reattributes the purchase to a referral unless cross domain measurement is configured.
Currency mismatch, or a currency code omitted from the purchase event.
Duplicate purchases, which push GA4 above Shopify rather than below.
Test orders, draft orders, POS and manually created orders, which exist in Shopify and never touch the storefront.
How to reconcile on purchase count
Start with purchase count, not revenue. Count is a discrete, ID matched event, and it separates the collection question from the definition question.
Pick a calendar month that has closed. In Shopify, get order count for that month, excluding test orders, draft orders and any POS or manual channel. In GA4, get purchase event count for the same month, with the property timezone set to the store timezone.
Then reconcile at the ID level. Export order IDs from Shopify. Export transaction_id values from GA4 using an exploration on the purchase event. Compare the two lists. This turns a vague percentage into a named list of missing orders, and missing orders almost always share a property: same payment method, same device class, same country, same landing path.
Judge the gap on count. As a working rule, a few percent below Shopify is the normal residue of consent, blockers and lost hits. Ten percent or more is a bug worth hunting. GA4 above Shopify is always a bug, and usually duplication.
Only once count is understood should you look at revenue. Rebuild Shopify’s figure to match your GA4 definition: if the dataLayer sends totals including tax and shipping, compare against total sales rather than net sales, and add refunds back if you are not sending refund events. If revenue still disagrees after count reconciles and definitions align, the value on individual events is wrong.
Finally, watch the checkout directly. Place a low value real order and check the network panel for a GA4 collect request carrying the purchase event. If it never appears, no amount of report tuning will help.
How to fix Shopify tracking gaps
Set the GA4 property timezone to the store timezone. One setting, and an entire class of daily discrepancy disappears. Note that GA4 does not reprocess history when you change it, so the improvement starts from the change date.
Wire the purchase event through the supported customer events layer for your checkout rather than through the theme. On modern Shopify checkouts that is the mechanism that actually executes. The tradeoff is that the sandboxed environment restricts what your code can reach, so read order data from the event payload rather than from the page.
Send transaction_id equal to the Shopify order ID on every purchase, and send a refund event with the same ID when an order is refunded. That gives you an order level key to reconcile against, it lets Google Ads discard a repeat conversion carrying an order ID it has already recorded, and it keeps the revenue line tracking Shopify over time instead of drifting.
Choose one revenue definition and write it down. Most teams pick the amount the customer actually paid, including shipping and tax, because that is what the ad platforms optimize against. Whichever you pick, document which Shopify figure it should be compared with, so the next person to run the comparison does not start over.
Configure cross domain measurement for any off-domain payment step, then confirm it by checking that purchases from that flow are not attributed to a referral from the payment provider.
Implement Consent Mode v2 rather than hard blocking tags. A tag that is blocked outright sends nothing and can contribute nothing to modeling. A consent aware tag communicates the consent state, which is what allows behavioral and conversion modeling to operate. This describes measurement behavior only. How you configure consent for your jurisdiction is a question for your legal counsel, not for a tracking consultant.
What missing orders cost you
The cost is not the reporting gap. It is what the ad platforms do with a partial signal.
If GA4 sees a subset of purchases, every optimization built on GA4 inherits whatever bias that subset carries. Consent denial and tracking prevention are not evenly distributed across browsers, devices, countries or audiences. The orders GA4 misses are not a random sample. Bidding systems trained on the surviving sample bid down the segments that under-report and bid up the segments that report cleanly, regardless of which ones make money.
Attribution suffers twice. Missing purchases strip credit from the channel that earned them, and cross domain breakage hands credit to a payment provider referral. Both push budget toward channels that merely survive measurement well.
Then there is the cost of the argument. Finance trusts Shopify. Marketing quotes GA4. Every planning cycle spends time relitigating which number is real instead of deciding what to do about it.
Reconciling GA4 against Shopify is a fixed procedure rather than a judgment call: separate definition differences from collection failures, match at the order ID level, and name exactly which orders are missing and why. That reconciliation is part of every Missing Conversions Audit, and the checkout, consent and cross domain gaps it turns up are logged with what each one costs, with the top 10 fixed and validated before delivery.
Questions
How close should GA4 and Shopify be?
Compare purchase count rather than revenue, in matching timezones, with test and draft orders excluded. A few percent below Shopify is the expected residue of consent denial, ad blockers and dropped hits. Ten percent or more usually means a real collection failure. GA4 reporting more purchases than Shopify has orders is always a bug.
Why is GA4 revenue higher than Shopify net sales?
Two likely reasons. Your purchase event probably sends the order total including shipping and tax, which should be compared against total sales rather than net sales. And GA4 does not subtract refunds unless you send a refund event with the matching transaction_id, so the gap widens as refunds accumulate.
Why do GA4 sessions not match Shopify sessions?
They are different objects. Shopify derives sessions from its own storefront traffic records; GA4 derives them from the JavaScript tag, with its own timeout, bot filtering and rules for restarting a session when campaign parameters change. Neither is a check on the other. Reconcile on purchases, which have a shared identifier.
Why do purchases go missing on Shopify checkout specifically?
Modern Shopify checkouts do not execute arbitrary theme scripts. Third party measurement has to run through the supported customer events layer. A GTM container installed only in the theme will track the storefront correctly and then go silent at exactly the point that matters, which produces a large and consistent purchase shortfall.
Next step
Find out what this is costing you.
The Missing Conversions Audit is a fixed-price teardown of your GA4, Google Tag Manager, ad pixels and consent setup. Every gap logged, the top 10 fixed and validated. $900 flat. 5 business days.
A 15 minute call
- We look at your setup from the outside and say what we can already see.
- You get a straight answer on whether the audit is worth it for your account.
- If it fits, we book the slot and send the access checklist.
Opens the calendar here. Nothing loads from Calendly until you do.
Prefer email? hello@missingconversions.com