← work
ecommerce, shopify (NDA) · 2025 [CHECK] · GTM, GA4, Shopify Customer Events, Google Ads, Meta Conversions API

A Shopify store double-counting purchases across three systems

-2.2%
revenue variance vs. Shopify — was +33.9% pre-fix
-1.8%
purchase variance vs. Shopify — was +32.4% pre-fix
<1%
duplicate purchase rate — was ~30% pre-fix
problem:

A mid-sized Shopify retailer had inherited years of overlapping tracking work from multiple agencies. GTM, Shopify Customer Events, and Shopify's own Google & YouTube integration were all capable of sending the same purchase event, and GA4 was running 33.9% ahead of Shopify's own order data — a variance too large to explain by consent gaps or attribution alone.

 

Three separate pathways were quietly capable of reporting the same customer behavior: Shopify's native Google & YouTube app sent straight to GA4, Shopify Customer Events piped through a custom pixel and GTM to that same GA4 property, and legacy theme code from an earlier build was still firing its own GTM events on top of both. Nobody had removed what came before — each agency's work made sense in isolation, but no one had reconciled it against what was already there.

 

The numbers made the case concrete: for the period under review, Shopify's own order data showed 2,410 orders worth $286,000. GA4 reported 3,190 purchases worth $383,000 — 32.4% more orders and 33.9% more revenue than actually happened. On a store spending roughly $180,000 a month across Google Ads and Meta, every bidding and budget decision was being made against numbers that were a third too high.

what i built:

I mapped every pathway capable of emitting a purchase event — GTM, Shopify Customer Events, the Google & YouTube app, theme code, and third-party app scripts — then reconciled individual transaction IDs against Shopify orders to confirm which pathways were duplicating.

 

Before changing a single tag, I pulled a handful of real orders and checked their transaction IDs against GA4's purchase events — the moment the same transaction ID showed up through more than one pathway, this stopped being an analytics-variance question and became a concrete instrumentation bug. Running an actual test order through GTM Preview made the scale of it obvious: one checkout produced four differently-named events — purchase, ecommerce_purchase, transactionComplete, and checkout_completed — each one written by a different agency, layered on top of whatever the last agency had left behind.

 

Rather than deleting tags one at a time, I established a single authoritative owner for each ecommerce event: Shopify's native app stayed responsible for GA4 ecommerce, GTM took everything else — marketing pixels, UX analytics, and custom business events — and every other route into GA4 was removed.

 

Every purchase was required to carry a validated transaction_id, and I checked the full set of ecommerce parameters — currency, value, tax, shipping, coupon, and line items — so the cleanup fixed the purchase count without leaving item-level reporting quietly broken. I re-tested the entire purchase flow end to end before calling it done.

purchase {
  transaction_id: string   // dedup key, required
  currency: string         // ISO-4217
  value: number            // net, excl. tax + shipping
  tax: number
  shipping: number
  coupon: string
  items: array       // item_id, item_name, quantity, price
}
outcome:

GA4 revenue moved from 33.9% ahead of Shopify to -2.2%, well inside normal variance from consent and browser restrictions.

 

The underlying business hadn't shrunk — Shopify's own order count grew slightly over the same window, from 2,410 orders worth $286,000 to 2,537 orders worth $301,400. What closed the gap was GA4 coming down to meet it: reported purchases fell from an inflated 3,190 to 2,491, and reported revenue from $383,000 to $294,900 — a purchase variance of -1.8% instead of +32.4%.

 

Reported Google Ads ROAS dropped from an inflated 6.2 to an accurate 4.9 within 60 days, closely matching the retailer's real blended return of 4.8. CPA fell from $42 to $37 as budget stopped chasing conversions that weren't real, and the duplicate-purchase rate — running around 30% before the fix — dropped to under 1%.

 

The same audit caught inflated events higher up the funnel: with sessions flat at 100,000, add-to-cart events had been running at roughly 1.7x reality (12,800 reported vs. 7,500 real) and begin-checkout at about 1.4x (7,600 vs. 5,300). The original numbers pointed at cart-to-checkout as the weak point in the funnel; the corrected numbers showed the real opportunity was earlier, at product-view-to-add-to-cart — which reset the CRO roadmap.

 

Once the retailer was bidding on real numbers, one Shopping campaign that had looked like a top performer turned out to be mediocre, while a different, underfunded campaign was quietly producing better customers. Budget was reallocated accordingly.

[CHECK] [screenshot] the server container