Shop Pay, Apple Pay, PayPal: Why GA4 Misses Revenue

A side-by-side screenshot of a Shopify admin Orders page on the left and a Google Analytics 4 Ecommerce purchases report on the right for the same 14-day window, with three editorial annotations marked up on top. Shopify shows 247 total orders worth $42,180.30 with an average order value of $170.77. GA4 shows 188 purchases worth $33,860.20 with an average revenue per purchase of $180.11. Order rows visible on the Shopify side include #SH1247 Sarah K. Shop Pay paid $156.40, #SH1246 James M. Apple Pay paid $89.20, #SH1245 Lina T. Shop Pay paid $312.00, #SH1244 Marc P. PayPal Express paid $74.80. A caption at the top reads 'Same 14-day window. Same store. Shopify counted 247 orders worth $42,180. GA4 saw 188 purchases worth $33,860. The 59 missing orders are the express-checkout gap.' One annotation reads '247 vs 188 orders. The 24% gap is the express-checkout signal loss. Littledata's February 2025 benchmark says 20 of every 100 Shopify orders fail to appear in GA4 on average.' Another reads 'Shop Pay alone was 41% of Shopify's Q4 2024 gross payment volume. Its express path completes on shop.app or pay.shopify.com, where the merchant's GA4 tag does not execute.' A third reads '$8,320 in missing revenue across the 14-day window. The gap follows Shop Pay, Apple Pay, and PayPal usage patterns, not random.'
Show article contentsHide article contents
  1. What we found in 27 stores (and why 78% are in the gap)
  2. How we ran the 27-store audit
  3. Shop Pay: where the purchase event goes to die
  4. Apple Pay: the OS modal your tracker can't see into
  5. PayPal Express: redirects, popups, and the 15% who never come back
  6. The consent layer that compounds every gap
  7. Why the $450/month server-side pipe barely moved the numbers
  8. Revenue Gap Calculator
  9. The two jobs you're accidentally asking one tool to do
  10. Decoupling attribution from the payment sheet: the architecture
  11. What this looks like in Clickport (and what it doesn't)
  12. Frequently asked questions
  13. Keep reading

Your Shopify admin and GA4 never agree on revenue. Every store owner has seen the gap, and most assume it's GA4 being GA4. It isn't. The orders go missing at the checkout domain, not in the report. Shop Pay processes 41% of Shopify's payment volume, and its checkout runs on a domain your analytics can't reach. We crawled 27 top DTC Shopify brands in April 2026 and 78% of them route checkout through shop.app, pay.shopify.com, or checkout.shopify.com. That's most of the best-known stores on the platform, sending buyers somewhere the merchant's own tag never loads. Only one store in the sample had a third-party server-side tracking pipe installed. The rest are in the gap. And the pipe every vendor sells you to close it does not close it.

Key Takeaways
  • Shop Pay represented 41% of Shopify's Q4 2024 gross payment volume. Its express path does not reliably fire GA4's purchase event, a behavior Shopify staff confirmed as expected in the developer forums.
  • In our April 2026 audit of 27 top DTC Shopify brands, 78% route checkout through shop.app, pay.shopify.com, or checkout.shopify.com, where the merchant's own JavaScript cannot execute.
  • Only 1 of 27 audited brands visibly installed a third-party server-side tracking pipe. The $200 to $990 per month rescue ecosystem is ignored by 96% of the sample.
  • Server-side Google Tag Manager relays events that already fired. It cannot create events that never fired. The express checkout gap is an event origination problem, not a transport problem.
  • Shopify's orders/paid webhook fires reliably for every completed order including Shop Pay, Apple Pay, and PayPal, regardless of ad blockers, consent state, or payment-sheet JavaScript context.

What we found in 27 stores (and why 78% are in the gap)

The mechanics come later. First, the audit. On April 14, 2026 we crawled the public HTML of 27 well-known DTC Shopify brands across apparel, beauty and personal care, home goods, and food and beverage. Each one is a name you would recognize in its category, with real traffic and an active paid-social presence. We are not publishing the store list. The point is a platform-level problem, not a pile-on against any one brand. What matters is the shape of the sample. These are the stores an agency or a DTC founder would point to as best-in-class.

Clickport Audit, n=27, 2026-04-14
What we found in the public HTML of 27 top DTC Shopify brands
78%
route checkout through shop.app, pay.shopify.com, or checkout.shopify.com
74%
have Shopify's Web Pixel sandbox active, restricting third-party tracking access
4%
visibly installed a third-party server-side tracking pipe (Elevar, Stape, Analyzify, Littledata)
Detection is conservative. Tracker IDs hidden inside Shopify's Web Pixel sandbox are not always visible in SSR HTML. What the crawl sees is what a third-party auditor or ad-blocker heuristic would see. Full methodology below.

The 78% is the number to sit with. That's 21 of the 27 brands whose cart page already points at a Shopify-controlled checkout domain before the buyer does anything. We opened the cart HTML of the four most-trafficked brands by hand to be sure. All four embed shop.app/checkouts/internal/preloads right there in the markup. So this is not an edge case that fires only when someone taps Shop Pay. Shopify is pre-warming the shop.app checkout as the default path for these stores.

The 4% is the one that surprised me. Every ecommerce tracking article ranking on Google tells you to install a server-side pipe: Elevar, Stape, Littledata, Analyzify. We found one store out of 27 running any of them. Zero Elevar, zero Stape, zero Analyzify, one Littledata. That means 96% of the best-known stores on the platform are ignoring the entire $200 to $990 per month rescue industry. Not because they don't care about their data. Because the pipe does not fix the problem people think it fixes. I get to that in section six.

For a deeper view of where Shopify's native analytics fall short, see Shopify Analytics: The Store Owner's Guide to Better Data.

How we ran the 27-store audit

Here is the full method, so you can check the results above and rerun them yourself.

Sample. 27 publicly known DTC Shopify brands, pulled from commonly cited Shopify case studies and industry lists, then filtered down to the ones we could confirm were running on Shopify (by detecting cdn.shopify.com, Shopify.theme, or shopify-section markers in the homepage HTML). The mix spans apparel (9 stores), beauty and personal care (8), food and beverage (7), home goods (2), and one personal-care-adjacent store. No store with under 50k monthly visits made the cut. We leaned toward brands that already show up in industry reporting, so the sample reflects best-in-class DTC setup, not long-tail merchants. We are holding the store list back to keep the finding on the platform-level gap, not on arguments about any one brand's data point.

What we fetched. For each store, three public endpoints:

  • The homepage (/)
  • The cart page (/cart)
  • The Shopify-default /products.json?limit=1 to get one product handle, then the product page (/products/{handle}) where the JSON endpoint returned data

No login. No items added to cart. No checkout started. No personal data touched. Every request used a Safari 17 desktop user-agent string, a 15 to 25 second timeout, and a 1.5 second pause between stores.

What we looked for. The crawler grepped each store's combined HTML for regex signatures of the tracking stack, express checkout path, and third-party pipes:

  • GA4 measurement ID: G-[A-Z0-9]{6,12}, gtag/js?id=G-, "accountID":"G-", google-analytics.com/g/collect, "measurementId":"G-"
  • Google Tag Manager: GTM-[A-Z0-9]{4,10}, googletagmanager.com/(gtm|gtag).js
  • Meta Pixel: connect.facebook.net, fbevents, fbq(, facebook.com/tr?, "pixelId": followed by 10+ digits, facebook-domain-verification
  • TikTok Pixel: analytics.tiktok.com, ttq.load, ttq.page, tiktok.com/i18n/pixel
  • Klaviyo: static.klaviyo.com, klaviyo.com/onsite, _learnq, klaviyoOnsite
  • Shopify native analytics: shopify-analytics, monorail-edge.shopifysvc, trekkie.storefront
  • Web Pixel sandbox active: web-pixels-manager, webPixelsManager, web_pixels_manager
  • Third-party server-side pipes: getelevar.com, shopify.elevar.io, littledata.io, analyzify.com, stape.io, gtm.stape, .stape.net
  • Express checkout domains: shop.app, pay.shopify.com, checkout.shopify.com
  • Express checkout buttons: shopify-payment-button, shop-pay-button, shop_pay, apple-pay-button, ApplePaySession, paypal.com/sdk, paypal-button
  • Headless detection: /products.json?limit=1 returning non-JSON was treated as headless or JSON endpoint blocked

Limits, stated up front. Three of them matter, and I want them on the table before you read the numbers.

  1. Sandboxed pixel IDs do not always show up in SSR HTML. Shopify's Web Pixel sandbox renders the tracking config inside iframed JSON that gets hydrated client-side. A store running GA4 through the sandbox can show web-pixels-manager signatures with no visible G-XXXXXX measurement ID in the static HTML. So our GA4, Meta Pixel, and TikTok counts are floors, not ceilings. The real numbers can only be higher.
  2. Headless storefronts push tracking even further client-side. Four stores in the sample (15%) run Next.js on top of a Shopify backend. Their marketing HTML is hydrated by React, which often loads tracking after the fact. For these stores, the SSR signal is at its weakest.
  3. A third-party pipe can hide when the merchant routes sGTM through a custom subdomain. We searched for vendor-owned domains (stape.io, elevar.io, littledata.io, analyzify.com) and for common sGTM patterns. A merchant who has cleanly CNAMEd their sGTM container to a fully custom subdomain will not match any of these. So the 1-of-27 figure is a floor for third-party sGTM adoption. The true number could be higher. It will not be much higher, because most setups leave at least one vendor-identifiable string in the HTML.

What the crawl tells us reliably. Which domain the checkout points at (shop.app, pay.shopify.com, checkout.shopify.com). Whether the Web Pixel sandbox is active, which is a strong yes-or-no signal. Whether a third-party pipe is visibly branded in the markup. And which express checkout buttons are declared. Those are the four signals the headline findings rest on.

Full results, all signals, n=27
Signal detected in public HTMLCount%
References shop.app / pay.shopify.com / checkout.shopify.com2178%
Web Pixel sandbox active2074%
Shopify native analytics (monorail / trekkie)2074%
PayPal integration detected1867%
Klaviyo1763%
GTM container1556%
Meta Pixel visible in SSR HTML1452%
GA4 measurement ID visible in SSR HTML1141%
Apple Pay button detected622%
Shop Pay button detected622%
Headless storefront (products.json blocked)415%
TikTok Pixel14%
Third-party server-side pipe (Elevar / Stape / Analyzify / Littledata)14%
Only 1 of 27 stores shows a visibly branded third-party server-side pipe (Littledata signature in the HTML). Zero Elevar, Stape, or Analyzify signatures detected. True third-party sGTM adoption could be marginally higher if a merchant has fully CNAMEd their sGTM container, but our detection is deliberately broad.

Spot-check. We opened the cart page HTML of the four most-trafficked brands by hand, to make sure the 78% was not just the regex seeing things. All four embed the string shop.app/checkouts/internal/preloads directly in their cart markup. Shopify is pre-warming the off-domain checkout as the default path for these brands. This is not a corner case that needs a specific buyer to tap Shop Pay first.

Reproducibility. The whole audit is 80 lines of bash and Python. Curl each store's three URLs, pipe the combined HTML through grep -E for each regex above, write a row to CSV, aggregate with value_counts(). Anyone with a developer laptop and two hours can run it against their own sample and compare.

What we will rerun. October 2026, same 27 stores, same regexes, published as a before-and-after diff. Two numbers I expect to move. The share of stores in full headless mode should go up. So should the share with the Web Pixel sandbox hiding pixel IDs, as more merchants migrate off checkout.liquid.

Shop Pay: where the purchase event goes to die

Shop Pay was 38% of Shopify's gross payment volume across all of 2024, and it peaked at 41% in Q4, per PYMNTS coverage of Shopify's Q4 2024 earnings call. Shopify's own BFCM 2024 report says Shop Pay sales grew 58% year over year across the BFCM weekend. So this is the most-used express checkout in ecommerce. It is also the one that breaks your analytics the hardest.

Here is what happens when a buyer taps Shop Pay on a product page. Shopify redirects the session to shop.app or pay.shopify.com, depending on the flow. The merchant's JavaScript does not run there. GA4's tag, the Meta Pixel, any GTM container, the Klaviyo onsite tracker, all of them stop at the domain line. The checkout steps run on a domain the merchant does not own and cannot put scripts on. When the buyer finishes, Shopify sends them back to the merchant's own thank-you page. That return trip was the one thing meant to save client-side tracking. It doesn't.

A Shopify staff member, Alan_G, confirmed in the Shopify developer community that the checkout funnel events, checkout_started, checkout_shipping_info_submitted, payment_info_submitted, do not fire in the Shop Pay view. His exact words: "at the moment, it's expected behaviour that the events don't fire in the Shop Pay view," with the team putting the fix "on our radar to enable tracking in the medium-term." No date. The thread is from June 2025. As I write this, no fix has shipped.

The checkout_completed event is the one that maps to GA4's purchase event and Meta's Purchase. It fires only when the buyer lands on the merchant's thank-you page and the merchant's pixel gets its chance to run. For Shop Pay, that has been hit or miss. Merchants in the same forum thread report the event firing with an order total of zero, or not firing at all, or firing for some orders and quietly dropping others. There is no single number for how often it works. The failure is not consistent enough to have one.

What the buyer sees vs. what your pixel sees
THE BUYER
Taps Shop Pay, confirms via SMS code, sees a confirmation screen, gets the order email. From their perspective, checkout worked perfectly.
YOUR PIXEL
Saw `add_to_cart` on the product page. Saw nothing else. The buyer left the merchant domain before any checkout events could fire, and the return trip to the thank-you page is either non-deterministic, stripped of session context, or happens after the pixel has already given up.
Source: Shopify dev community (June 2025), Shop Pay and Web Pixel Events thread.

There is a second failure mode, and it is worse because it makes no noise. In October 2025, a merchant named Jason on the Shopify community reported that turning on Shop Pay Installments (Pay in 3) silently switched his checkout domain from brownieheaven.co.uk to shop.app. His Meta Pixel was domain-verified on brownieheaven.co.uk, so the verified-domain check now failed. Purchase events stopped firing to Meta's CAPI. He burned £175 in ad spend over five days before he worked out why. Shopify gave him no warning before flipping the switch. In the end he turned Pay in 3 off to get his attribution back.

No merchant could have set this up to avoid it. The domain switch was Shopify's product decision, and it tore up the merchant's domain verification chain. A Shop Pay feature update can break your Meta tracking tomorrow. You will hear about it from your ROAS dashboard, not from a Shopify email.

A screenshot of the Google Analytics 4 Traffic acquisition report with the property selector blurred behind a grey redaction bar, three editorial annotations marked up on top. The table shows eight session source / medium rows including google / organic (4,210 sessions), (direct) / (none) (3,024), pay.shopify.com / referral (612), shop.app / referral (488), facebook.com / referral (318), newsletter / email (280), paypal.com / referral (142), and instagram.com / referral (96). Three rows are highlighted in pale yellow: pay.shopify.com, shop.app, and paypal.com. A caption at the top reads 'The three rows your campaigns paid for, stolen by your own checkout domain. pay.shopify.com, shop.app, and paypal.com show up as referral channels and overwrite the original Meta or Google session attribution.' One annotation reads 'pay.shopify.com is the SMS-code authentication step for Shop Pay. It becomes the last referrer on the thank-you page and steals credit from whatever channel actually brought the buyer in.' Another reads 'shop.app is Shopify's checkout pre-warm domain. 78% of the 27 DTC brands we audited route through it. The merchant's GA4 tag does not run there.' A third reads 'paypal.com / referral. The GA4 cross-domain _gl linker is stripped by the HTTP redirect. Without it, GA4 attributes the order to PayPal instead of Meta, Google, or email.'
The three checkout-domain referral rows that quietly overwrite your real attribution. The fix is the GA4 referral exclusion list at Admin → Data Streams → Configure tag settings → List unwanted referrals. The longer-term fix is the architecture in the next section.

Apple Pay: the OS modal your tracker can't see into

Apple Pay on the web is not an iframe. It is not a pop-up. It is a browser-native modal that Safari and the OS draw, fully outside the merchant page's document. Apple's ApplePaySession documentation describes the flow in terms of "the system" showing the payment sheet, not the merchant page. The merchant page never owns the sheet.

So your JavaScript event listeners cannot see what the buyer does inside it. The ApplePaySession API does expose events for merchant validation, shipping changes, and the final paymentauthorized callback, but those fire only at set hand-off points. The biometric check (Face ID, Touch ID, passcode) happens inside the Secure Enclave. The tap on "Pay" happens inside the sheet. Your page JavaScript gets paymentauthorized after the Secure Enclave says the payment is good, along with the encrypted token. Everything in between is dark.

Take a visitor who taps Apple Pay, passes the biometric check, then closes the tab or loses signal before the thank-you page loads. The merchant's pixel sees nothing. No add_payment_info, no purchase, no checkout_completed. Shopify's official docs confirm that accelerated checkout buttons can sit right on the product page, skipping the cart entirely. The button HTML renders inside a closed shadow DOM. Any tracking attached to the DOM fails without a sound.

Then ITP stacks on top. Safari's Intelligent Tracking Prevention caps JavaScript-set first-party cookies at 7 days, and at 24 hours if the landing URL carried a link-decoration parameter like fbclid or gclid. The GA4 client ID cookie and Meta's _fbp cookie are both script-set first-party cookies. So a buyer who comes back after 8 days looks brand new to your analytics. Apple Pay does not cause this on its own, but the two feed each other. The people most likely to reach for Apple Pay are mobile Safari users, which is exactly where ITP bites hardest. Per StatCounter (March 2026), Safari holds about 55% of US mobile browser share and 43.86% of UK mobile share.

One number to correct while we are here. The "53% Safari UK mobile" figure you see quoted everywhere is wrong. StatCounter's own dashboard shows 43.86%. Use the real one.

Warning
A cookie consent banner cannot gate the Apple Pay sheet. The sheet is a browser-native OS modal, not merchant-side JavaScript. Your CMP cannot block it, your visitor cannot be asked about it before it appears, and Apple Pay payment data flows regardless of the consent state your merchant site is in. This is the only part of the ecommerce stack that operates outside GDPR's client-side consent model by design.

PayPal Express: redirects, popups, and the 15% who never come back

PayPal's Smart Buttons run three different flows, depending on the device and the browser. On desktop with popups allowed, checkout opens in a pop-up window. On mobile, or when the popup is blocked, PayPal falls back to a full-page redirect to paypal.com. Inside an iframe there is a third path. PayPal's own developer documentation calls the popup the main mode and the redirect "older." In practice, every mobile Shopify checkout takes the redirect.

When a mobile buyer finishes on paypal.com and PayPal 302-redirects back to the merchant's thank-you page, the browser sends Referer: https://www.paypal.com/ along with it. GA4's cross-domain linker parameter (_gl) gets stripped by that HTTP-level redirect. Google's own cross-domain documentation admits the parameter "may end up being removed from the URL." Seresa.io put the mechanism plainly: "JavaScript cannot intercept an HTTP server redirect. That's the fatal gap." Without _gl, GA4 starts a fresh session and credits paypal.com as the referrer, overwriting whatever Meta, Google, or email campaign really brought the buyer in.

Then there are the 10 to 15% who never come back at all. MarketLytics measured this across their own GA setups: "In about 85-90% of cases, user returns to the site successfully and we can trigger a transaction. But those 10-15% of times a user closes the page after completing the order, is where the trouble lies." The payment cleared. The order is sitting in Shopify's admin. The purchase event never fired, because there was no thank-you page load for it to fire on.

PayPal's PPCP integration makes Venmo checkout worse still. Braintree's own Venmo documentation says the Mobile Web Fallback flow "currently requires a full page redirect." On mobile the standard flow switches over to the Venmo app, then comes back, and Braintree notes that "some mobile browsers can return customers to the same tab, while others relaunch your checkout page in a new tab." In practice that means the merchant has to rebuild the session on return. Every Venmo payment through PPCP goes through either a full-page redirect or an app-switch. So your session cookie, your linker parameter, and your client ID all have to survive a path that crosses a server redirect or a native app context. Often they don't.

PayPal Smart Button behavior by context
Desktop, popup OK
Popup window, session stays on merchant domain, `purchase` event fires on return with referrer largely intact
Mobile
Full-page redirect to paypal.com, `_gl` linker stripped, 10-15% never return to thank-you page, original attribution overwritten to paypal.com referral
Venmo (PPCP)
App-switch to Venmo iOS/Android app or Mobile Web Fallback full redirect, then browser return, tab handling varies by browser so session restoration is required
Safari popup blocked
Documented cases of the PayPal popup failing to open in Safari, leaving the buyer unable to complete checkout at all (BigCommerce Checkout SDK issue #944)
Return-failure rate of 10-15% is MarketLytics' own instrumented data. PPCP Venmo behavior is from Braintree's client-side JS documentation.

Before checkout even starts, a real share of your visitors have already said no to cookies. That is GDPR working as designed, and it is not going away.

The USENIX Security 2024 paper by Bielova et al. ran controlled banner tests on French users. When accept and reject got equal prominence, 34% rejected. When the banner spelled out the consequences of sharing data, that climbed to 47%. Put plainly, give people a fair choice and a third to nearly half of them opt out. etracker's 2025 consent benchmark for Germany measured 60% data loss under a legally compliant banner. Weight that for the EU share of a typical DTC audience and your GA4 and Meta Pixel coverage is already down 15 to 30% before a single express checkout enters the picture.

The SHEIN fine makes the legal stakes real. CNIL issued a €150 million penalty on September 1, 2025, announced on the 3rd. The violation was not a missing reject button. The reject button was there. It just did not work. Cookies still landed after "Refuse all," and pre-existing ones kept on reading. So "has a reject button" is now the floor. The bar regulators enforce is "does the reject button do anything."

On a Shopify store, rejecting the banner does more than hide the analytics data. Shopify's Web Pixels API documentation states that the pixel manager will not load a pixel that requires marketing or analytics consent when that consent is missing. The pixel never loads, so the cookieless modeling pings that Google Consent Mode v2 leans on to fill gaps cannot fire from inside the sandbox either. That visitor is not a 50% signal you can model your way back to. They are a zero.

The stacked funnel: 100 EU visitors, one Shopify store with compliant consent + express checkout
100 visitors
Real traffic
After consent rejection
~40 tracked
After ad blockers
~30
After express checkout
~21 in GA4
Consent rejection ~60% per etracker 2025 (Germany). Ad blockers 29.5% global per Backlinko (Q2 2025). Express checkout loss ~30% per Littledata (Feb 2025). Illustrative compounding, not a controlled study.

For more on why cookie-banner-free tracking matters architecturally, see What Cookie-Banner-Free Analytics Actually Means. For the full picture on GA4 gaps beyond checkout, see Why GA4 Is Not Showing Data, or use the GA4 Data Loss Estimator to score the general accuracy gap by ad blockers, consent, Safari ITP, and GA4's internal limits.

Why the $450/month server-side pipe barely moved the numbers

Server-side tracking is the answer every article ranking for this topic gives you. Here is how it works. sGTM (server-side Google Tag Manager) runs on infrastructure you control, takes hits from the browser or from Shopify webhooks, and forwards them on to GA4, Meta CAPI, and the rest. In theory it gets around ad blockers, resets cookie expiry, and recovers lost events.

In practice it does some of that, not all of it, and the price is not what the blogs let on. Elevar charges $200/month for the 1,000-order tier, $450/month for 10,000 orders, $950/month for 50,000 orders. Their Expert Installation add-on starts at $1,000 one-time. Littledata starts at $199/month for 1,500 orders and climbs to $990/month at 10,000. Analyzify lists $109 to $206/month, with a server-side tagging add-on of $1,490 to $2,790 one-time, depending on whether you self-host on Google Cloud or use a Stape-hosted container. Stape is the infrastructure-only pick at $17 to $167/month on annual billing, plus Google Cloud Run hosting that realistically adds $60 to $240/month on top, depending on your traffic. So the real bill is higher than the headline number, every time.

Now the upside. Stape's own published case study recovered 20.71% of previously lost requests over a 10-day test, and 30.67% of purchase events specifically. Those are genuinely good numbers for what sGTM does, which is recover requests the browser made but that got blocked on the way out. They tell you nothing about what sGTM cannot do.

And what it cannot do is make an event that never happened. If Shop Pay does not fire checkout_completed, because the Shopify staff engineer said it is not expected to, there is no hit for sGTM to relay. The Shopify Web Pixel sandbox decides what events leave the checkout domain. sGTM sits downstream of the sandbox. It is a better pipe, not a power source. It moves events that exist. It does not create the ones that don't.

Our audit of 27 brands found 1 store with a visible third-party pipe installed. Everyone else runs Shopify's native Meta, Google, and TikTok integrations, which live inside the same Web Pixel sandbox the sGTM pipes are trying to route around. I am not saying these tools are useless. They really do recover 20 to 30% of otherwise-lost events. I am saying 96% of this sample decided that recovery was not worth $200 to $990 a month, and looking at where the gap comes from, they have a point.

What server-side GTM does and does not fix
ProblemsGTM / CAPI
Ad blocker blocks browser requestFixed (routes via first-party domain)
Safari ITP caps cookie to 7 daysPartially fixed (server-set cookies can be longer-lived)
Shop Pay `checkout_completed` event never firesNot fixed (nothing to relay)
Apple Pay sheet, buyer closes tab pre-thank-youNot fixed (no client hit)
PayPal mobile redirect, `_gl` strippedNot fixed (attribution lost before server relay)
Consent banner rejection (EU)Not fixed (sandbox suppresses firing entirely)
Shop App upsell on `shop.app/thankyou`Not fixed (order never recorded on merchant domain)
Pixel + CAPI event deduplicationWorks if `event_id` matched on both sides; frequently broken
Pipes relay. They do not resurrect events that never existed.

Revenue Gap Calculator

Plug in your own numbers. The math is open and the source stats are linked right below the widget.

Revenue Gap Calculator
Monthly Shopify revenue$100,000
EU visitors (consent banner region)30%
Express checkout usage (Shop Pay + Apple Pay + PayPal)35%
Safari mobile traffic share25%
Visitors with ad blockers23%
Estimated monthly revenue invisible to GA4 / Meta Pixel
$42,380
/month your analytics can't see
Consent rejection loss
Express checkout event loss
Browser tracking loss (ITP + blockers)
Consent 45% loss on EU share: etracker 2025. Express checkout 30% loss: Littledata Feb 2025. Ad blocker 50% signal loss: Backlinko Q2 2025. Clickport captures this via server-side order webhooks. See how it works.

The two jobs you're accidentally asking one tool to do

Here is the reframe. Your Shopify admin is the source of truth for revenue. Every completed order is in it. The orders/paid webhook fires for every one, whether the buyer used Shop Pay, Apple Pay, PayPal, or the plain checkout. Shopify does not lose orders. Shopify loses analytics events.

Your analytics, done right, is the source of truth for one thing: which channel earned each order. Not whether the order happened. Not how much it was worth. Those are Shopify's job. The analytics job is narrower. When an order ID shows up in Shopify, which session started it, and where did that session come from.

GA4 tries to do both jobs at once. It counts orders and attributes channels in the same system, and it uses the same client-side purchase event to tie the two together. So when that event fails to fire at the payment sheet, both jobs fail in the same instant. You lose the revenue count and the channel attribution together, because the one event that carried both is gone.

The decoupling argument is simple. Count orders where orders get counted, in the Shopify admin. Capture the channel where the channel is knowable, at landing, before the payment sheet even exists. Then join the two by order ID the moment the webhook fires. The payment sheet drops out of the critical path entirely.

Edward Upton, Littledata's CEO, has been writing about this gap for years, just never framed this way. Brad Redding of Elevar said it bluntly on the Honest Ecommerce podcast in October 2022: "I don't think the vast majority of any site out there is going to say, 'Yes, my Google Analytics is 100% accurate.'" Rand Fishkin put the bigger version of it on the SparkToro blog in July 2024: "If you try to prove acquisition via traffic referral data you will become Google's fool." So the industry already agrees on the diagnosis. The prescription it keeps writing is "ship more events, faster." That is the one thing the payment sheet refuses to go along with.

Decoupling attribution from the payment sheet: the architecture

Here is what it looks like in practice, on a Shopify store, with any analytics tool that has a server-side custom event API.

Step one: capture the session source at landing. When a visitor arrives, a first-party script reads the URL's UTM parameters, the referrer, and any click IDs (gclid, fbclid). It writes them into a session record on the server, keyed by a short-lived first-party ID. If the tool is cookieless, the session ID lives in sessionStorage for the tab's lifetime, or gets regenerated on each visit. If it is cookie-based, a first-party cookie handles it. Either way, the channel is locked in before checkout matters at all. (This only works if the inbound tags are clean; the UTM builder grades yours and shows the channel they'll classify into.)

Step two: at add-to-cart or begin-checkout, the storefront writes that session context into Shopify's note_attributes on the cart. This is the move the whole thing turns on. note_attributes is a key-value array that Shopify keeps with the order. Whatever you put there rides through the Shop Pay redirect, the Apple Pay sheet, the PayPal popup, and the trip back. When the order shows up in the admin, your session context is sitting right there in order.note_attributes.

Step three: Shopify fires the orders/paid webhook. Not orders/create, which fires for unpaid orders too. orders/paid is the real "money received" event. Your handler verifies the HMAC, reads the payload, pulls the session context out of note_attributes, and fires a server-side custom event to your analytics tool with the full revenue amount, the currency, the order ID, and the channel.

import crypto from 'crypto';

export async function handleShopifyWebhook(req) {
  const rawBody = await req.text();
  const hmacHeader = req.headers.get('x-shopify-hmac-sha256');

  const digest = crypto
    .createHmac('sha256', process.env.SHOPIFY_CLIENT_SECRET)
    .update(rawBody)
    .digest('base64');

  if (!crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(hmacHeader))) {
    return new Response('Unauthorized', { status: 401 });
  }

  const order = JSON.parse(rawBody);
  const ctx = Object.fromEntries(
    (order.note_attributes || []).map(a => [a.name, a.value])
  );

  await fetch('https://clickport.io/api/event', {
    method: 'POST',
    headers: {
      'X-API-Key': process.env.CLICKPORT_API_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      type: 'custom',
      name: 'Purchase',
      url: order.landing_site || `https://${order.domain}/orders/${order.id}`,
      revenue_amount: parseFloat(order.total_price),
      revenue_currency: order.currency,
      meta_keys: ['order_id', 'utm_source', 'utm_medium', 'utm_campaign', 'payment_method'],
      meta_values: [
        String(order.id),
        ctx.utm_source || '',
        ctx.utm_medium || '',
        ctx.utm_campaign || '',
        order.payment_gateway_names?.[0] || ''
      ]
    })
  });

  return new Response('OK', { status: 200 });
}

That is 30 lines, HMAC check included. It runs as a Cloudflare Worker, a Vercel function, a Shopify App webhook handler, or a plain Express route. The /api/event endpoint is documented in Clickport's custom events documentation. The Shopify webhook registration and field reference live in Shopify's Admin API docs.

A screenshot of a Shopify admin Order detail page for order #SH1247 with three editorial annotations marked up on top. The order shows a 'Paid' green badge and a 'Fulfilled' green badge, placed on May 14, 2026 at 11:42 via Shop Pay for $156.40 USD. The right sidebar's Additional details card contains a Note attributes block listing six key-value pairs: utm_source = facebook, utm_medium = paid_social, utm_campaign = spring_apparel_2026, utm_content = wool_throw_carousel_v3, landing_page = /products/wool-throw-charcoal, cp_session_id = 8f3a-2b1d-44e9-7c2a. A caption at the top reads 'The fix the article describes. The session's UTM context written into note_attributes at add-to-cart survived the Shop Pay redirect, the express checkout sheet, and the return trip. When the orders/paid webhook fires, the channel attribution is right there in the payload.' One annotation reads 'Shop Pay completed off-domain. The merchant's GA4 tag did not fire on this order. Without note_attributes, this Purchase would arrive in Shopify with no channel context attached.' Another reads 'utm_source=facebook, utm_campaign=spring_apparel_2026. Written into the cart at add-to-cart, persisted through the Shop Pay redirect, returned in the orders/paid webhook payload.' A third reads 'When orders/paid fires, the webhook handler reads note_attributes, fires a server-side Purchase event with the original channel, and joins the revenue to the session by order ID instead of by cookie.'
What the fix looks like inside Shopify. The session's UTMs and the Clickport session ID land in note_attributes when the cart is built, ride through the Shop Pay redirect inside Shopify's own order object, and come out the other side as part of the orders/paid webhook payload. The channel attribution survived the payment sheet.

Here is what this buys you that server-side GTM cannot. The server-side event does not wait on any client-side event firing. If Shop Pay fires nothing, if Apple Pay suspended the tab before the thank-you page, if PayPal never came back from paypal.com, it makes no difference. Shopify delivered the order to its admin. The webhook fired. The server-to-server event carried the session context across. The join happened on order ID, not on a cookie.

And here is what it does not buy you. It does not fix Meta Ads Manager's ROAS dashboard. Meta's optimization engine still wants Pixel and CAPI events with proper deduplication, and that is a separate problem with a separate fix. What this architecture gives you is a true internal picture of which channel earned which revenue. That is the picture you make decisions on.

What this looks like in Clickport (and what it doesn't)

Clickport is a cookieless analytics product. The tracker is 5.6 KB, loads from a first-party domain, and writes a session ID to sessionStorage that lives for the tab only and clears on close. No persistent cookies. No consent banner needed. The 16-channel classifier runs server-side and catches AI Search, social referrers, paid click IDs, and affiliate parameters that GA4 keeps dumping into "Direct." The custom events API takes the webhook payload above as-is.

Here is what the architecture above gives you inside Clickport. Every completed Shopify order shows up as a Purchase event with the original session's channel attached. You can segment revenue by channel and watch the $47,000 GA4 filed under "Direct" last month break out into 31% Meta paid social, 22% Google organic, 18% email, and 11% AI Search referrals from ChatGPT and Perplexity. The numbers hold up because the channel was captured before the payment sheet had a chance to break anything.

What it does not do, where I want to be straight with you:

  1. Clickport does not replace Shopify's admin as the revenue ledger. We read your orders. Shopify still owns them. If Shopify says $120,000 this month and Clickport says $119,800, Shopify wins. Every time.
  2. Shop App upsell orders completed on shop.app/thankyou cannot be recovered by any analytics vendor, us included. The buyer's session sits inside Shopify's domain, the upsell happens entirely there, and the merchant's webhook fires with the order but loses the upsell's channel context before anyone can grab it. That is a platform-level limit, not a tool problem.
  3. We do not do Meta CAPI forwarding or pixel deduplication out of the box. If you need your Meta Ads Manager ROAS to reflect the recovered events, that takes a separate CAPI setup. Shopify's native Meta channel is the easiest option there, Stape's sGTM the more robust one. Clickport gives you your internal attribution picture. It does not feed Meta's bidding signal.
  4. Our Shopify integration is a documented pattern, not an installed app. The code above is how real customers wire it up. There is no one-click app store install today. If you are a technical team or an agency, this is an afternoon of work. If you need a click-install, I am not going to pretend we are there yet.

So here is the honest summary. Clickport solves the part of this problem the rest of the ecosystem is worst at: attributing revenue to the right channel after the payment sheet has broken the client-side event chain. It does not solve the parts the ecosystem already does well, like Meta ROAS dashboards. Different tools for different jobs. That is the whole point of the decoupling argument, and I think it holds for a lot of Shopify stores, not just ours.

Start a 30-day free trial. No credit card. The tracker installs in 60 seconds, and the webhook handler above is the second thing you ship.

Frequently asked questions

Why is my GA4 showing fewer purchases than Shopify?

Shopify's admin counts every server-confirmed order. GA4's purchase event fires only when the thank-you page loads in the buyer's browser and the client-side script gets to run. Ad blockers, consent banner rejections, Safari ITP, Shop Pay domain hops, Apple Pay's OS-level sheet, and PayPal's mobile redirect all break that chain. A 10 to 20% gap is the industry baseline. Above 20% points to a config problem. Above 30% usually means express checkouts are a big share of how your customers pay.

Why is Shop Pay not tracking in GA4?

Shop Pay's express path finishes on shop.app or pay.shopify.com, domains where your GA4 tag does not run. Shopify's developer staff confirmed in June 2025 that checkout_started, checkout_shipping_info_submitted, and payment_info_submitted events "don't fire in the Shop Pay view" as "expected behaviour." The thank-you page can still get a checkout_completed event if the buyer comes back to your domain, but that is hit or miss. Add shopify.com and shop.app to your GA4 referral exclusion list to limit the attribution damage, and fire a server-side event from the orders/paid webhook if you want a revenue count you can trust.

Does Apple Pay break GA4 tracking?

Apple Pay's payment sheet is an OS-level modal that Safari and the operating system draw, outside the merchant page's document. Your JavaScript cannot see the biometric check or the tap on "Pay." If the buyer closes the tab before the thank-you page loads, the purchase event never fires. ITP's 7-day cookie cap makes it worse for returning visitors. Apple Pay on mobile Safari is one of the highest-volume, least-trackable payment paths in ecommerce.

Why does GA4 show Direct for purchases that came from my Meta ads?

Two causes stack here. First, the checkout flow hops through payment.shopify.com or paypal.com, and those become the last referrer on the thank-you page, overwriting your original Meta attribution. Second, GA4's cross-domain _gl linker parameter gets stripped by HTTP-level server redirects, so session continuity snaps at the domain hop. Any session with no UTMs and no valid referrer falls into direct / (none). Two fixes: add shopify.com, shop.app, and paypal.com to your GA4 referral exclusions, and capture UTMs into Shopify's note_attributes before the buyer ever leaves your storefront.

What does pay.shopify.com mean in my GA4 referral sources?

When a buyer confirms Shop Pay through the SMS code modal on pay.shopify.com, that domain becomes the last touchpoint before your thank-you page loads. Brad Redding of Elevar documented this pattern years ago: pay.shopify.com turns up as its own referral channel and steals credit from the campaign that really earned the sale. Fix: GA4 Admin → Data Streams → Configure tag settings → List unwanted referrals → add shopify.com and shop.app.

Is it normal for GA4 to be 20% short?

Littledata's February 2025 benchmark says 20 of every 100 Shopify orders never show up in GA4, on average. That is with a standard client-side setup. Individual merchants have reported gaps as high as 84% (Shopify community thread, January 2025). A 10 to 20% gap you can live with. A 30%-plus gap means express checkouts are a real chunk of your payment volume, and you need a server-side purchase event fired from the Shopify webhook.

Keep reading

On WooCommerce instead of Shopify? The same purchase event goes missing, but the fix is within reach there because the checkout runs on your own server: WooCommerce Sales Not Tracking in GA4? We Audited 75 Stores. For the wider Shopify tracking picture, start with Shopify Analytics: The Store Owner's Guide to Better Data. For a deeper look at what GA4 hides past checkout, Why GA4 Is Not Showing Data is the companion piece. If you want the channel classification problem across all your traffic, not just ecommerce, Attribution Modeling: The Six Models with Actual Math shows why the "Direct" bucket is the worst lie in your dashboard. And for the cookie-banner argument sitting under all of this, What Cookie-Banner-Free Analytics Actually Means lays out the regulatory math.

We will rerun the 27-store audit in October 2026 and publish the diff.

David Karpik

David Karpik

Founder of Clickport Analytics
Building privacy-focused analytics for website owners who respect their visitors.

Comments

Loading comments...

Leave a comment