Nothing erodes trust in analytics faster than a revenue number that is quietly too high. Double-counted purchases do exactly that: they inflate revenue, break ROAS, and make every downstream decision a little bit wrong. Here is why the purchase event fires twice and how to stop it.
How GA4 deduplication actually works
GA4 will deduplicate purchases that share the same transaction_id, but the protection is limited: it works only for web streams, not app streams, and it holds for a limited window rather than forever. So transaction_id is a backstop, not a cure. And there is a trap – if you send an empty string, GA4 treats every transaction_id="" as the same transaction and collapses unrelated orders into one. Always send a real, unique ID:
gtag('event', 'purchase', {
transaction_id: 'T_12345', // unique per order, never empty
value: 59.90,
currency: 'USD',
items: [{ item_id: 'SKU_1', item_name: 'Plan', price: 59.90 }]
});
The usual causes
Deduplication only masks the symptom. The real fix is to stop firing twice in the first place. The common culprits are:
- A thank-you page that reloads or gets refreshed, firing the event again.
- The GA4 tag installed twice – for example in GTM and in the theme, or via the Shopify Google app on top of GTM.
- A GTM trigger set to fire on every event instead of “Once per page.”
- A server-side event duplicating the browser event.
- A leftover Universal Analytics snippet still running alongside GA4.
Fix the firing, then keep the backstop
Work through the causes in order, then let transaction_id catch anything that slips:
- Set the GTM purchase trigger to “Once per page” so a reload within the same load cannot re-fire it.
- Audit for duplicate tags – you should have exactly one GA4 configuration for the property. Remove the theme snippet or the Shopify app tag if GTM already handles it.
- If you track server-side and client-side, send the purchase from one side only, or make the server handler idempotent on transaction_id.
- Guard the thank-you page against reloads with a localStorage check.
// Suppress a repeat purchase on reload of the confirmation page
var orderId = 'T_12345';
if (!localStorage.getItem('ga4_purchase_' + orderId)) {
gtag('event', 'purchase', { transaction_id: orderId, value: 59.90, currency: 'USD' });
localStorage.setItem('ga4_purchase_' + orderId, '1');
}
How to confirm it's fixed
Use DebugView and complete a test order, watching that exactly one purchase fires. Then refresh the confirmation page and confirm no second event appears. In your reports, compare GA4 transactions against your backend order count for a few days – they should line up closely, allowing for consent-declined traffic. If GA4 is higher, you still have a duplicate path somewhere.
A revenue number that is quietly too high is worse than no number, because people act on it. Send a real unique transaction_id, remove duplicate tags, set triggers to fire once, guard the reload, and verify in DebugView. Do that and GA4's revenue matches your books, so the trust you need in every ROAS decision comes back.
Want a stronger data analyst role or a raise? Grab the FREE Product Analyst Playbook and get the exact roadmap to your next offer.

