Skip to content
Ecom

GA4 for Shopify: What Actually Tracks (And What Silently Doesn't)

The four events GA4 misses on Shopify out of the box, how the server-side tagging fix works, and the consent mode v2 ordering trap most stores get wrong.

By WitsCode11 min read

Every Shopify store we audit has GA4 installed. Almost none of them have GA4 installed correctly. The Google and YouTube sales channel makes it look like a two-click job, and on the surface it is. Paste the measurement ID, toggle the consent integration, walk away. A week later the merchant opens the GA4 realtime view, sees sessions ticking up, and assumes everything is working. It usually is not.

The gap between what Shopify reports in its admin and what GA4 reports in the purchase event is almost always double-digit. We have seen stores with a 28 percent undercount in GA4 that nobody noticed for six months, because the numbers were internally consistent inside GA4 itself. The ads stack was bidding on bad data, the weekly revenue report to the founder was wrong, and the marketing team was making budget decisions on a skewed picture. This piece walks through the four events that silently fail on a default Shopify and GA4 install, the server-side tagging architecture that actually fixes them, and the Consent Mode v2 ordering requirement that is the number one reason European stores show zero events for half their traffic.

The native Shopify to GA4 connection is not a full ecommerce implementation

When you enable the Google channel inside Shopify, what you get is a web pixel that emits a handful of GA4 ecommerce events from the storefront. It sends page_view, view_item on the product detail page, add_to_cart on cart add, a version of begin_checkout, and purchase on the thank-you page. That is it. Compared to the full GA4 enhanced ecommerce specification, you are missing view_item_list on collection pages for most themes, select_item when a shopper clicks into a product card, reliable add_payment_info and add_shipping_info on the classic checkout, and the entire refund and return flow. view_promotion and select_promotion are not emitted. Custom events for subscriptions, wishlist adds, or quiz completions are out of scope for the native integration.

The purchase event itself, the one transactional event you absolutely cannot afford to lose, has its own quirks. It fires client-side on the thank-you page load. If the shopper closes the tab before the page fully renders, which happens more often than people think on flaky mobile networks, the event never reaches GA4 even though Shopify has the order. If the shopper refreshes the thank-you page, the event can double-fire, and unless the downstream tag dedupes on transaction_id, you now have inflated revenue. The out-of-the-box GA4 connection on Shopify is roughly a 60 to 70 percent implementation of enhanced ecommerce, and the missing portion is exactly the part that matters for attribution, returns, and lifetime value.

The four silent misses

The first miss is refunds. When a customer returns an item and you issue a refund inside the Shopify admin, no event is sent to GA4. The original purchase event stays on the books, the revenue figure never gets decremented, and the annualized revenue number you see in the Monetization report is inflated by whatever your return rate is. For apparel stores running 25 to 40 percent return rates, this is not a rounding error. It is a massive distortion that also flows out to Google Ads via the GA4 audience and conversion imports, teaching the bid algorithm that customers who later return are just as valuable as customers who keep the product.

The second miss is customer lifetime value attribution. Shopify's Customer Events pixel sandbox does not expose the customer.id field by default to the GA4 tag. That means the user_id parameter in GA4 is almost never populated on a stock install. Returning customers who clear cookies, switch devices, or come in through a different browser show up as brand new client_ids, and GA4's attempt to reconcile them through Google Signals is partial at best. Cohort analysis, LTV by acquisition source, and repeat purchase rate all degrade. The fix is small, a few lines in a custom Customer Events pixel that reads init.data.customer.id and passes it as user_id on every event, plus enabling the User-ID reporting identity in the GA4 property. The default install does not do this.

The third miss is Shop Pay cross-domain session splitting. When a returning shopper taps Shop Pay, they are handed off to shop.app or pay.shopify.com to complete the checkout. The GA4 _ga cookie is scoped to the original storefront domain and does not follow them across that hop. Without cross-domain linking explicitly configured in the GA4 tag, the shopper appears as two distinct sessions. The original session, the one that carries the campaign UTMs, ends when they leave the storefront. The purchase then attributes to the second session, whose source and medium is almost always shop.app / referral or pay.shopify.com / referral. Paid campaigns look like they are not converting. Organic and direct look inflated. Retargeting audiences leak. The fix is configuring the GA4 configuration tag's cross-domain list to include shop.app and pay.shopify.com alongside the primary storefront, and ensuring the _ga parameter is appended to the outbound links. Again, not default.

The fourth miss is ad-blocker loss. This is the one that is hardest to fix from the client side and easiest to underestimate. uBlock Origin, Brave's built-in shields, iOS content blockers, and various consent-respecting browser defaults all filter googletagmanager.com and google-analytics.com at the network layer. The GA4 library simply never loads. No events get queued, no events get sent. The typical Shopify store we audit sees a 15 to 25 percent undercount in GA4 purchase count versus the Shopify admin order count over the same window, and that gap is almost entirely ad-blocker loss plus the refresh and tab-close edge cases on the thank-you page. Stores that sell to developers, privacy-conscious niches, or European audiences can see 30 percent or more. You cannot fix this with client-side tagging alone. The only real remedy is to move the measurement endpoint to a first-party subdomain, which is the next section.

Server-side tagging is the fix, and it looks like this

The architecture that actually closes these gaps has four pieces and is worth spelling out end to end, because most tutorials skip the part where Shopify fits in.

Piece one is the Shopify Customer Events pixel. You create a custom web pixel in the Shopify admin, written in the pixel sandbox dialect, that subscribes to the standard events (product_viewed, product_added_to_cart, checkout_started, checkout_completed) and posts them to a first-party endpoint. That endpoint is a subdomain of your own store, something like metrics.yourstore.com, configured with a CNAME pointing to a server-side Google Tag Manager container. The first-party subdomain is the part that defeats most ad-blocker lists, because the blocker has no reason to suspect your own domain.

Piece two is the server-side GTM container itself. You can host it on Google Cloud Run, App Engine, or a managed provider like Stape or Addingwell. The container receives the hits from the pixel, enriches them, and forwards them to GA4 via the Measurement Protocol, to Google Ads via the conversions API, to Meta via CAPI, and to TikTok or wherever else.

Piece three is the webhook side channel, which is the bit most guides leave out. The Customer Events pixel still runs in the browser and is still vulnerable, to a lesser degree, to network failures and early tab closes. For the events that absolutely must land, purchase and refund specifically, you register Shopify webhooks (orders/paid and refunds/create) that post directly from Shopify's servers to a dedicated endpoint on your sGTM container. These webhook events carry the transaction_id, the customer_id, the line items, and the refund amount. Because they originate server to server, they cannot be blocked by anything on the user's device. The sGTM container dedupes them against any browser-side purchase event it already saw, using transaction_id as the key, and forwards a single clean event to GA4.

Piece four is the consent layer, which we are about to talk about on its own because it has its own failure mode.

The net result is typically a recovery of 12 to 18 percent of previously invisible conversions, a refund flow that actually decrements revenue in GA4, and attribution that holds together across the Shop Pay handoff. The gap you recover is not new sales. It is sales that were always happening but were invisible to your analytics and, more importantly, invisible to the machine-learning bidders you are paying Google and Meta to run.

Google made Consent Mode v2 effectively mandatory for EEA and UK traffic in March 2024. Advertisers who do not send the two new signals, ad_user_data and ad_personalization, alongside the existing ad_storage and analytics_storage, lose the ability to build remarketing audiences from European traffic and see progressively more degraded modeling in Google Ads. On Shopify, compliant implementation is trickier than the docs suggest, and the failure mode is subtle.

The rule that trips people up is ordering. The default consent state must be set before the GA4 library, or any gtag call, executes. That means the gtag('consent', 'default', {...}) call with everything denied for European visitors has to be the first thing in the head of the page. If the GA4 tag fires first and the default consent declaration arrives a few hundred milliseconds later, Google's backend treats the early hits as consented, which is both a GDPR violation and, more pragmatically, unreliable.

On Shopify, the obvious place to put the default consent call is inside the Customer Events pixel. This does not work. The Customer Events sandbox is asynchronous and loads after the page, which is too late. The correct place is a direct injection into theme.liquid, in the head, above every other script. The consent banner, whether that is Shopify's own Customer Privacy banner, Pandectes, iubenda, or Cookiebot, then loads and lets the user choose. On accept, the banner fires gtag('consent', 'update', {...}) with the relevant fields set to granted, and only then does the GA4 tag actually start sending hits through.

If you are using server-side tagging, the consent state needs to be forwarded to the sGTM container as well, because the Measurement Protocol calls from sGTM to GA4 need to carry the consent parameters. sGTM does this automatically if you are using the GA4 client template, but only if the original browser hit carried the consent signals, which only happens if the ordering was right in the first place.

We have audited stores where Consent Mode v2 was technically installed, the merchant saw the banner, the DevTools network panel showed the calls being made, and yet the GA4 property was reporting zero conversions from Germany. Every time, the root cause was identical. The default denied state arrived after the first gtag call, and the whole European dataset went dark. The fix is always to move the default consent declaration into theme.liquid, above everything.

The WitsCode analytics audit, and what it actually looks at

Every new Shopify client we onboard goes through a one-day analytics audit before we touch anything else. The audit is not glamorous. It is a spreadsheet. Four columns, one row per day for the last thirty days. Column one is the Shopify admin order count, pulled from the orders report. Column two is the GA4 purchase event count for the same day, pulled from the GA4 Explorer. Column three is the Google Ads reported conversion count for the same day. Column four is Meta's reported purchase count.

We compute the gap between column one, the ground truth, and each of the other three columns. A healthy, well-tagged Shopify store shows Shopify admin within five percent of GA4, and both ad platforms within ten percent of Shopify. That is the target. Most stores on a stock install show GA4 running 15 to 25 percent below Shopify, Google Ads running 20 to 40 percent below, and Meta often worse because of iOS tracking prevention on top of everything else. Any gap above ten percent between Shopify admin and GA4 is our trigger to recommend server-side tagging.

The audit also checks two qualitative things. First, whether Consent Mode v2 is correctly ordered, which we verify by loading the storefront in a fresh incognito session with the DevTools network tab open and watching the order of gtag calls. Second, whether the purchase event carries a transaction_id matching the Shopify order name, which we check by placing a test order and comparing the GA4 DebugView to the Shopify order confirmation. These two checks catch more than half of the issues we find. When a founder sees that they did nine hundred orders last month and GA4 reported six hundred and seventy, the conversation about sGTM becomes much shorter.

What to do this week if you cannot rebuild the stack

Not every store has the budget or the urgency to move to server-side tagging this quarter. If that is you, there are four small changes that will close a meaningful chunk of the gap without any infrastructure work.

First, add Shop Pay and pay.shopify.com to the cross-domain list in your GA4 configuration. This one change typically recovers ten to fifteen percent of attribution fidelity on stores that have Shop Pay turned on, which is most of them.

Second, write a small custom Customer Events pixel that passes customer.id as the GA4 user_id on every event, and turn on User-ID reporting in the GA4 property settings. This gets you real returning-customer identification without waiting for a full sGTM rollout.

Third, set up a Shopify Flow (or a one-file Cloudflare Worker) that listens to the refunds/create webhook and fires a Measurement Protocol call to GA4 with a refund event carrying the original transaction_id. This is maybe two hours of work and it fixes the single largest distortion in your revenue reporting.

Fourth, move your default Consent Mode v2 declaration into theme.liquid above every other script, if you serve any European traffic at all. This is purely a copy-paste job with real regulatory consequences if you skip it.

None of these four fixes require server-side GTM. They all push your GA4 implementation meaningfully closer to reality. When you are ready for the full server-side build, the above work is not thrown away. It is exactly the foundation the sGTM container will read from.

The pattern we have seen across hundreds of Shopify audits is simple. GA4 on Shopify is not broken. It is underinstalled. The native integration gets you started, but the four silent misses, refunds, LTV identity, Shop Pay cross-domain, and ad-blocker loss, are the reason your numbers never match your admin. Fix those four and your analytics stop lying to your bidders, your reports, and your board.

Get weekly field notes.

Practical writing on shipping products, straight to your inbox. No spam.

Need help with this?

Shopify Development

We design and build web apps, MVPs, and SaaS products. Talk to us about what you are working on.

Talk to us

Want to discuss ecom for your business?

Start a project and we'll talk through where you are, what's working, and the highest-leverage moves for the next 90 days.