TRACKING SPECIALIST — BARIS ASA · LONDON
Synctag
Start the check → TRACKING SPECIALIST — BARIS ASA · LONDON
F-101
F-101 · COLLECTION & DUPLICATION · FILED 2026 · REVISED 15 JUN 2026 · FREQ ▮▮▮▮▯

Duplicate conversion firing

Also filed as “ga4 revenue higher than shopify”“ga4 purchases counted twice”“meta reports more sales than the store”“google ads conversions higher than actual orders”“purchase event firing twice in tag assistant”“thank you page counting conversion on reload”

Duplicate conversion firing is one real order being recorded as a conversion more than once, because the purchase tag sends the same event two or more times. The order is genuine and singular. Only the count is wrong, so reported purchases run ahead of real orders, revenue inflates against the back office, and any ad-platform ROAS built on those conversions inflates with it.

Symptom and cause

Symptom
Purchases counted twice; revenue inflated on Meta and Google Ads, and in GA4 too once transaction_id goes missing. ROAS looks great, until finance disagrees.
Cause
The purchase tag fires more than once per order: a theme update double-injects it, a tag lives in both GTM and the platform, or a thank-you page reloads.
Where it's caught
Tag Assistant / GA4 DebugView: two purchase events with the same transaction_id. Or GA4 revenue running ahead of the store's own figure.

How one order gets counted twice

The tag carrying the purchase fires more than once for a single order, and each fire is its own hit. The usual sources: the tag is wired to more than one trigger, or to an over-broad trigger like All Custom Events; gtag.js is hardcoded in the theme and the same tag also lives in GTM, so two containers load and every event doubles (common on Shopify when a theme and an app both inject GTM); a single-page checkout re-pushes the purchase on a route change; or the thank-you page is reloaded, bookmarked or reached by the back button, replaying the hit. What each platform then does depends on the dedup key. GA4 deduplicates purchase events that carry the same transaction_id (web streams only, and within an undocumented processing window, so do not promise it is multi-day), which is why a stable, unique transaction_id quietly absorbs many double-fires and the visible overcount tends to land on Meta or Ads instead. Meta's Pixel and Conversions API deduplicate when the browser and server send the same event_id and the same event_name (case-sensitive, "purchase" is not "Purchase") within 48 hours, counting only one of the two, and dedup is most reliable when the browser event arrives first and the server event second. A missing or mismatched event_id means the browser and server copies both count. Google Ads treats a second conversion on the same action with the same transaction_id (order id) as a duplicate and drops it. Two traps run the other way: an empty transaction_id makes GA4 collapse all empty-id purchases into one (an undercount, not an overcount), and a hardcoded or static id makes Ads reject genuine second orders. The overcount is a per-fire-changing id or a missing key on a true double-fire, or, on Meta and Ads, any double-fire without a shared dedup key.

DevTools · Network · collect for one checkout · reconstructed · client data anonymised
t+mseventtransaction_idsource
0page_viewGTM · GA4
+1180add_payment_infoGTM · GA4
+2090purchase████-4471GTM · GA4 tag
+2130purchase████-4471hardcoded gtag

How to catch it

  1. Confirm it is a double-fire, not reconciliation drift, before touching anything. Set the platform's reported purchases and revenue for a window against the store back office (Shopify, Woo, the order table). If conversions run ahead of real orders and the conversion rate looks implausibly high, it is a counting fault, proceed. If each platform fires only once per order but the browser and server totals still double because they share no dedup key, that is F-102, not this.
  2. Run the definitive two-hits test in DevTools, Network. Complete one real or test purchase, then filter on collect for GA4 and on /tr for the Meta Pixel. Two collect requests for one order, carrying the same transaction_id (and for Meta the same event_id) milliseconds apart, is the smoking gun. Switch on Preserve log first so the hits survive the thank-you redirect.
  3. Replay the purchase in GA4 DebugView (enable it through Tag Assistant Companion) and look for two purchase events with an identical transaction_id at the same timestamp. In Tag Assistant or GTM Preview the same duplicate fire shows on the timeline. This is the literal exhibit for this fault.
  4. Count the fires in the GTM Preview Summary tab. Clear the stream, perform the purchase once, open Summary and click the purchase tag: how many times did it fire? Two means a tag or trigger problem inside GTM. One means the duplicate is coming from outside GTM, a hardcoded gtag, a second container or a replayed dataLayer push. Do the final read with Preview closed and in a fresh live session to confirm production behaviour, since Preview can make some auto-event triggers (link clicks, history changes) appear twice on the debug timeline even when the tag fired once.
  5. Hunt the double load in the page source and Network. Search for gtm.js, for G-, gtag( and the AW- and pixel IDs. Two gtm.js loads, or a hardcoded gtag alongside GTM, or two containers, or a theme and an app both injecting GTM, doubles every tag on the page, not only purchase.
  6. Check the trigger and the SPA path. In GTM, confirm the purchase tag is attached to one trigger and that trigger fires only on the genuine purchase dataLayer push, not All Custom Events. Then reload the thank-you page and press the back button while watching DebugView and Network: if purchase re-fires on reload or route change, that is the SPA or reload refire. Also check GA4 Enhanced Measurement Page changes based on browser history events against any custom page_view you push.

What putting it right involves

  1. Make one source of truth per destination. Pick a single deployment path: if GTM owns the GA4, Ads or Meta tag, remove the hardcoded gtag from the theme, and the other way round. Remove any second GTM container, and where a theme and an app both inject GTM keep one. This alone fixes most everything-is-doubled cases, because it stops the structural double load at the root.
  2. Set transaction_id on every purchase, stable and unique. Generate it server-side at order creation and pass the same value to the tag on the confirmation page. Never empty (GA4 collapses all empty-id purchases into one), never hardcoded (Ads rejects genuine second orders), never regenerated per fire. A real, repeated transaction_id is what lets GA4 and Google Ads dedup do their job.
  3. Tame the trigger, the SPA and the reload. Tighten the GTM trigger to the genuine purchase dataLayer push and attach the tag to one trigger only, set to fire once per event. For single-page checkouts, trigger off router events rather than component lifecycle, debounce the route handler, and disable Enhanced Measurement Page changes based on browser history events if you push your own page_views.
  4. Add a reload-and-bookmark guard. Store fired transaction_ids in a first-party cookie or localStorage and block the purchase tag when the id is already present. The GTM recipe: a dataLayer variable for the id, a Custom JS that writes seen ids to a cookie, a cookie variable, and a trigger exception so the tag fires only when the id has not been stored yet. Do the write-back from a tag sequenced after the GA4 tag (tag sequencing) or from the trigger logic, not from a hit callback (the GTM GA4 tag exposes none to user code; in raw gtag.js the equivalent is event_callback, not the old Universal Analytics hitCallback). This catches the thank-you reload that the trigger fix alone cannot.
  5. Share one dedup key across browser and server. For Meta, derive a single event_id at source from the order id and send the same event_id and event_name from the Pixel and the Conversions API. Matching keys dedupe within Meta's window (~48 hours, as the mechanism notes), so timing is not the lever, the shared event_id is. Sending the server event promptly, and after the browser event, is good practice and makes dedup most reliable, but it is not required for the match. Then watch the dedup rate climb in Events Manager. Where the issue is specifically two systems not reconciling, that is F-102; this step is here only to stop the same single event self-doubling.
  6. Verify the fix end to end. Re-run the Network and DebugView checks with Preview closed and in a fresh live session: one collect hit per purchase, one purchase event per transaction_id, a healthy Meta dedup rate. If a duplicate purchase fire still shows with Preview closed, treat it as real, a second load you have not found, not a Preview artefact. Then re-set the platform counts against the store for the test window and confirm reported purchases now match real orders.
How I'd fix this

Sources on file

Cited in these case files

F-101 turns up as evidence across the archive. It is cited in:

Questions on this file

How do I tell F-101 (duplicate firing) apart from F-102 (signals not deduplicated)?

Count the fires for one platform first. F-101 is a single platform sending the same event more than once: in DevTools you see two collect hits, or in the GTM Preview Summary the purchase tag fired twice, for one order. F-102 is two systems, the browser and the server, both reporting the same conversion because they never shared a dedup key, so each side fires once but the total double-counts across them. The test: complete one purchase and read the Network panel. Two hits from the same source milliseconds apart is F-101. One browser hit and one server hit with mismatched or missing event_id is F-102. They can co-exist, so check both.

Why is my GA4 revenue higher than Shopify but Meta and Ads look even worse?

Because GA4 quietly absorbs many duplicates that Meta and Ads do not. GA4 deduplicates purchase events that share the same transaction_id, so as long as that id is stable and unique a double-fire often collapses back to one in the standard reports. The moment the id goes missing or changes per fire, GA4 starts double-counting too. Meta dedup needs a matching event_id across browser and server, and Ads needs the order id on the conversion, so when those are absent the overcount shows there first. The store back office is the only count that is not a tag, so reconcile against it.

The thank-you page counts a conversion every time it reloads. How do I stop that?

A reload, a bookmark or the back button replays whatever fires on page load, including the purchase hit. Two fixes together: set the purchase tag to fire once per page on the genuine purchase dataLayer push, and add a guard that records fired transaction_ids in a first-party cookie or localStorage and blocks the tag when the id is already stored. The guard is what survives a hard reload, because the trigger setting alone resets with the page. Keying on transaction_id is what makes the guard reliable.

Will setting Google Ads Count to One fix duplicate conversions?

Not on its own, and it is a different lever. Count One limits an ad click to a single counted conversion, which is the right setting for lead generation but wrong for ecommerce, where one click can lead to genuine repeat purchases. The mechanism that stops the same order being counted twice is the transaction_id (order id) on the conversion, which Google treats as a duplicate signal. For ecommerce use a unique transaction_id and leave Count on Every. Choosing the Count setting is really a key-event counting decision, which is F-104 territory, not this fault.

I removed the duplicate but DebugView still shows two purchase events. What did I miss?

Do the read with Preview closed and in a fresh live session first, because Preview can make certain auto-event triggers (link clicks, history changes) appear twice on the debug timeline even though the tag fires once, which is easy to misread as a double-fire. If the duplicate is gone once you confirm on the live site, the fix held. If two purchase hits persist with Preview closed, treat it as real: you have a second load you have not found yet, so search the page source for a hardcoded gtag, a second gtm.js, or a theme-and-app pair both injecting GTM.

Does an empty transaction_id cause double-counting in GA4?

No, it causes the opposite, which is why it is worth knowing. Google warns that GA4 deduplicates all purchase events sent with transaction_id set to an empty string, so many real orders collapse into effectively one and you undercount. A missing key on a true double-fire, or an id that changes on every fire, is what produces the overcount. Three states to keep straight: empty or static id collapses (undercount), changing or absent id on a double-fire inflates (overcount), and a stable unique repeated id deduplicates correctly, which is the state you want.