Hunor.digital
← All writing
GA4 · Tracking

GA4 duplicate transactions: find the leak, then fix that one

GA4 revenue higher than your shop's? Four mechanisms create duplicate purchases: thank-you page refreshes, double tags, missing transaction IDs, and SPA re-fires. How to prove which one is yours.

Lázár HunorDigital Fixer
The short answer

GA4 duplicate transactions come from four mechanisms: the purchase event firing on every thank-you page load including refreshes, the same tag implemented twice, transaction_id missing or non-unique so GA4 cannot deduplicate, and single-page checkouts re-firing on route changes. Diagnose first by counting repeated transaction_ids in an exploration, then apply the one fix that matches.

When GA4 reports more revenue than your shop took, the diagnosis is almost never exotic. Purchases are being counted more than once, one of four mechanisms is doing it, and each mechanism has a different fix. The expensive mistake is skipping the diagnosis: teams that skip it end up "fixing" duplicate transactions repeatedly, each round patching a mechanism they did not have.

Inflated revenue is not a cosmetic bug, either. It flows into ROAS, into "what channel deserves budget," into the bidding signals ad platforms optimize on. A store making decisions on 15 percent phantom revenue is quietly misallocating real money.

First, prove you have duplicates (and which kind)

Do not start in the tag manager. Start with evidence:

  1. Open a GA4 exploration, free-form table, dimension transaction_id, metric event count filtered to purchase (or transactions). Sort descending.
  2. Any transaction_id with a count above one is a smoking gun, and the pattern tells you the mechanism: the same id twice within seconds points at double tags; the same id hours or days apart points at thank-you page revisits; every id exactly once but totals still high vs the backend points at missing ids or a different event problem entirely.
  3. If the id column shows "(not set)", stop. Your purchase event carries no transaction_id, which is both a cause of duplication and the reason you cannot measure it. That is mechanism three, fix it first.

Five minutes, and you know which chapter below applies. That id-by-id audit is also the foundation of the monthly reconciliation habit from the e-commerce tracking spec.

Mechanism 1: the thank-you page fires on every visit

The classic. The purchase event triggers on the order-confirmation URL, so refreshes, back-buttons, bookmarked receipts, and "let me screenshot that for my accountant" all mint new revenue. It is the most common cause I find, and the one that produces duplicates hours apart.

The fix has layers. Best: fire purchase from a backend-driven dataLayer push that the platform emits once per order, not from "URL contains /thank-you". Good: keep the URL trigger but gate it, using the platform's first-visit flag or a one-time flag your checkout sets. Regardless: GA4 does deduplicate identical transaction_ids within a limited window on the same client, which softens same-session refreshes but does nothing against next-day revisits or cleared cookies. Dedup is a seatbelt, not a fix.

Mechanism 2: the same tag, twice

GA4 via GTM plus GA4 hardcoded in the theme plus GA4 inside a platform plugin. Two of the three fire purchase, and every order counts double within seconds. This one you confirm in the network tab on a test order: two purchase requests, milliseconds apart, often with different client implementations visible in the payload.

The fix is subtraction. Pick the one implementation that owns e-commerce (my default: GTM reading the dataLayer, per GTM vs hardcoded tracking), and remove the others. Audit plugins especially; shop plugins love to helpfully inject GA4.

Mechanism 3: transaction_id missing or fake

No id, and GA4 has nothing to deduplicate on; an id like "order" or a timestamp, and deduplication is theater. This mechanism also hides the other three, because without unique ids the exploration audit above cannot see repeats. The fix belongs in the dataLayer contract: the backend's real order number, unique per order, on every purchase push. While you are in there, confirm value and currency come from the same backend source, per the e-commerce spec.

Mechanism 4: single-page checkout re-fires

Modern checkouts are little SPAs. The confirmation "page" is a route, and route lifecycles can replay: a component remounts, a status poll re-renders the confirmation view, and the purchase push runs again within the same page load. The signature is duplicates seconds apart without two competing tag implementations. The fix is a guard in the push logic (fire once per order id per load) and the general SPA discipline from GA4 on single-page apps.

After the fix: make it stay fixed

Re-run the exploration a week later, then put two habits in place. Monthly: reconcile GA4 purchases against the backend order count and track the percentage; the number should be below 100 and stable, for the structural reasons covered in the tool-agnostic verification guide. After every checkout deploy: one test order with the network tab open. Duplicates are a regression-prone bug class because checkouts get rebuilt often and nobody re-tests tracking. The teams that stay clean are not smarter; they just re-run the dumb test. The full ownership-and-contracts picture is the tracking implementation guide.