Duplicate transactions in GA4: why your revenue is double-counted
If GA4 revenue reads higher than your store, duplicate purchase events are the usual culprit — a refreshed thank-you page, two tags, or a retry. Here is how to find and stop them.
If your GA4 revenue is higher than your payment processor's, you almost certainly have duplicate purchase events. The three causes account for nearly every case: the confirmation page can be reloaded, two tags are firing the same purchase, or a server-side event is not deduplicating against the browser one. All three are diagnosable in about ten minutes, and all three are fixable.
Confirm it is actually duplication
Before debugging, establish the shape of the problem. Compare a full week of GA4 purchases against your order system:
- GA4 higher than orders — duplication. Continue.
- GA4 lower than orders — under-reporting from consent, blockers, or a broken tag. Different problem entirely.
- GA4 roughly double — a tag firing twice on every order, the most common pattern.
- GA4 slightly higher — refresh-driven duplicates on a subset of orders.
Then check the transaction ID distribution. If the same transaction_id appears on multiple purchase events, duplication is confirmed and you have the fingerprint you need.
Cause 1: The confirmation page is reloadable
The order-received page can be refreshed, bookmarked, revisited from browser history, or reopened from an email link. Each visit fires the purchase event again if nothing guards it.
GA4 does deduplicate on transaction_id to a degree, but this behaviour is not something to rely on as your only defence — and your advertising platforms are less forgiving.
Fix, in order of robustness:
-
Server-side flag. Mark the order as tracked when the event is emitted; never emit again for that order. Survives everything.
-
Session storage guard. Record the transaction ID in the browser and skip repeats:
var txn = 'T-10432'; if (sessionStorage.getItem('purchase_' + txn) === null) { sessionStorage.setItem('purchase_' + txn, '1'); window.dataLayer.push({ event: 'purchase', transaction_id: txn, /* ... */ }); }Simple, effective for refreshes, defeated by a new session.
-
A one-time redirect away from the confirmation URL. Effective, and worse for user experience.
Cause 2: Two tags firing the same purchase
The classic version: a purchase tag inside GTM and a hardcoded snippet in the theme or platform integration. Both fire once. The order counts twice.
This is invisible from inside the container, which is why teams miss it — the container looks correct because it is correct. The duplicate lives in the page template.
How to find it: load a confirmation page and count purchase requests in the network panel. Two collect requests with en=purchase means two tags. Then view the page source and look for tracking code outside your container.
Platform-specific variants worth checking:
- Shopify — the Google & YouTube channel app plus a hand-built GA4 purchase tag, or a legacy theme tag plus a Customer Events pixel. Details.
- WooCommerce — two plugins each with analytics enabled. Details.
- BigCommerce — the native GA4 integration alongside a GTM tag. Details.
Cause 3: Server-side events not deduplicating
If you send purchases from both the browser and your server, they must be recognisable as the same event.
For GA4's Measurement Protocol, that means the same transaction_id and the same client_id as the browser event. For Meta's Conversions API and TikTok's Events API, it means a shared event ID.
The most common mistake is generating the event ID independently on each side — a timestamp, a random value — so nothing pairs. Generate it once, at the source, and pass it to both paths.
Cause 4: Single-page apps re-firing on route change
Less common but genuinely confusing. In an SPA, a confirmation view rendered client-side may re-mount when the user navigates back and forward, re-firing the event without any page load. Guard on the transaction ID as above, and see tracking single-page apps.
Diagnosing in the data
If you have the BigQuery export, the definitive query takes a minute:
SELECT
(SELECT value.string_value FROM UNNEST(event_params)
WHERE key = 'transaction_id') AS txn,
COUNT(*) AS events
FROM analytics_123456789.events_*
WHERE _TABLE_SUFFIX BETWEEN '20270201' AND '20270228'
AND event_name = 'purchase'
GROUP BY txn
HAVING events > 1
ORDER BY events DESC
The result tells you not just that duplication exists but how it is distributed — a handful of orders with many events points at refreshes, while every order having exactly two points at a duplicate tag.
Do not forget the ad platforms
GA4 is rarely the only affected system. If a purchase tag double-fires, your Google Ads conversions and Meta purchases are usually inflated too — and those numbers drive bidding, so the cost is direct rather than cosmetic.
Fix the source, then verify each platform separately with a test order. Fixing GA4 alone leaves the expensive half of the problem in place.
After the fix
Reported revenue will fall. That is the fix working, but it will look like a performance drop in every dashboard, so:
- Annotate the date in your reporting.
- Tell stakeholders before you deploy, not after they notice.
- Re-baseline targets that were set against inflated numbers.
- Reconcile again a week later against your order system to confirm the gap closed rather than reversed.
FAQ
Why is my GA4 revenue higher than my actual sales?
Duplicate purchase events, in almost every case. Check whether the same transaction_id appears on more than one purchase event — that confirms it immediately.
Does GA4 automatically deduplicate transactions?
It applies some deduplication on transaction_id, but it is not a substitute for preventing the duplicate emit — and your ad platforms do not benefit from GA4's behaviour at all.
How do I stop purchase events firing on page refresh?
Guard the emit: flag the order as tracked server-side, or record the transaction ID in session storage and skip repeats. The server-side flag is the durable option.
Why do I have two purchase tags when my container looks clean?
Because the second one is usually outside the container — a platform integration or a hardcoded snippet in the theme. Count purchase requests in the network panel rather than trusting the container.
Will fixing duplicates reduce my conversions in Google Ads?
Yes, and it should — the previous number was inflated. Annotate the change and reset any targets that were calibrated against the wrong baseline.
Start with the outside check: the free tracking audit flags duplicate GA4 properties on any URL, which is the most common root cause.
See where your tracking stands
Run the same 13-check audit referenced in this post against any URL. No signup, results in seconds.