Home » Fix Duplicate Purchase Events in Shopify (GA4 & GTM)

How to Fix Duplicate Purchase Events in Shopify

By Wahiduzzaman Sohan, Google Ads & Analytics Consultant  ·  Published 3 August 2026  ·  Updated 3 August 2026  ·  8 min read

Key takeaways

  • Two causes cover the overwhelming majority of cases: parallel tracking systems, and a reloadable confirmation page.
  • GA4 does not deduplicate purchase events. Revenue is summed from every event received, so one transaction sent twice doubles its revenue contribution.
  • On a live Shopify audit I ran, duplicate purchase events were inflating reported conversions by 30% to 40%.
  • Fix the source first by disabling one of the parallel systems. Only then add a transaction ID guard for reloads.
  • You cannot repair historical GA4 data. Annotate the fix date and treat the before and after periods as separate datasets.

Duplicate purchase events in Shopify almost always come from two tracking systems running at once — Shopify’s native GA4 connection alongside a GTM purchase tag — or from the order confirmation page being reloaded. The fix is to pick one source of truth and then guard the remaining tag with a transaction ID check.

First, confirm you actually have duplicates

Do this before changing anything. There is a precise query that gives you a yes or no answer in about five minutes.

  1. In GA4, open Explore and create a blank free-form exploration.
  2. Set the dimension to Transaction ID and the metric to Event count.
  3. Add a filter: Event name exactly matches purchase.
  4. Sort by event count, descending.

Every transaction ID with a count above 1 is a confirmed duplicate. There is no interpretation required and no attribution nuance to argue about — one order generated more than one purchase event.

Cross-check the total too. Compare GA4 purchase events against Shopify’s order count for the same date range. In the audit that produced the 30% to 40% figure, GA4 was reporting roughly 1.35 purchase events per actual Shopify order.

If GA4 purchases exceed Shopify orders by more than a few percent, your reported revenue and ROAS are both wrong, and Smart Bidding has been optimising against inflated signals for as long as it’s been running.

Cause 1: Shopify's native integration and your GTM tag are both firing

This is the most common cause by a wide margin. Shopify’s Google & YouTube channel connects your store directly to GA4 and starts sending ecommerce events, including purchases. If someone later adds a GA4 purchase tag in GTM — or installs a tracking app that does — both send the same transaction with the same ID, and GA4 records both.

Nothing warns you. The native connection is configured in your Shopify admin, the GTM tag lives in a different interface, and neither has visibility of the other. On agency-managed stores this frequently happens because the two were set up by different people months apart.

Check both: in Shopify, look at your Google & YouTube channel settings for a connected GA4 property. In GTM, search your container for any tag with an event name of purchase.

Cause 2: the order confirmation page can be reloaded

The thank-you page is a normal URL. Customers refresh it, bookmark it, navigate back to it, and open it again from their order confirmation email. Every one of those page views re-fires an unguarded purchase tag.

This cause produces a smaller inflation than parallel systems — typically 3% to 8% — but it is persistent, it never shows up in a single test order, and it disproportionately affects mobile traffic where accidental back-navigation is common.

Cause 3: Checkout Extensibility moved where your code runs

Shopify has retired checkout.liquid in favour of Checkout Extensibility. Tracking that used to live in the checkout template or in Additional Scripts now belongs in a Custom Pixel, configured under Settings → Customer events.

This matters for duplicates in two ways. Migrations often leave the old implementation partially in place alongside the new pixel. And Custom Pixels run inside a sandboxed environment with no direct access to the parent page’s dataLayer, so a pixel written as though it can push to GTM will either fail silently or be paired with a fallback that ends up double-sending.

If your store migrated recently, audit both the old and new locations before assuming the new pixel is the only source.

Cause 4: apps with their own pixels

Upsell apps, reviews apps, loyalty apps, subscription apps and analytics apps frequently ship their own conversion tracking, enabled by default. Each one that fires a GA4 purchase event adds another duplicate.

The practical audit: open the confirmation page with GTM Preview and your browser’s network panel, complete a test order, and filter network requests for collect. Count how many GA4 collect requests carry en=purchase. That number should be exactly one.

The fix: one source of truth, then one guard

Order matters here. Deduplication logic layered on top of two competing systems is a patch over a structural problem, and it will break the next time someone touches either system.

Step 1 — pick one source and disable the other. If you need control over event parameters, custom dimensions or server-side routing, keep GTM and disconnect GA4 from Shopify’s Google & YouTube channel. If you want the simplest reliable setup and don’t need customisation, keep the native connection and delete the GTM purchase tag. Either is defensible. Running both is not.

Step 2 — guard the surviving tag against reloads. Add a transaction ID check so a given order can only ever send once from a given browser:

// GTM Custom JavaScript variable — "cjs - Purchase Not Yet Sent"
// Add as a trigger condition: cjs - Purchase Not Yet Sent  equals  true
function() {
  try {
    var id = {{DLV - ecommerce.transaction_id}};
    if (!id) return true;                    // no ID available: fail open
    var key = 'ga4_purchase_' + id;
    if (window.localStorage.getItem(key)) {
      return false;                          // already sent: block
    }
    window.localStorage.setItem(key, Date.now());
    return true;
  } catch (e) {
    return true;                             // storage unavailable: fail open
  }
}

Two deliberate choices in that code. It fails open, so a browser with storage disabled loses deduplication rather than losing the conversion — under-reporting is a worse outcome than a rare duplicate. And it keys on the transaction ID rather than a session flag, so a genuine second order from the same customer still tracks correctly.

If you would rather avoid the side effect of writing inside a variable, split it: read the flag in the trigger condition, and write it from a separate custom HTML tag attached to the same trigger using tag sequencing.

How to verify the fix

  1. Place a real test order. In GA4 DebugView, confirm exactly one purchase event with the correct transaction ID and revenue value.
  2. Reload the confirmation page three times. Confirm no additional purchase events appear.
  3. Navigate away and return via browser back. Confirm still no additional events.
  4. In your network panel, confirm exactly one collect request with en=purchase.
  5. Wait 24 to 48 hours, then re-run the transaction ID exploration from the top of this article. Every count should be 1.
  6. Compare GA4 purchases to Shopify orders for the period after the fix. Expect agreement within a few percent.

Then add a GA4 annotation on the fix date. Anyone comparing this quarter to last will otherwise see revenue appear to fall off a cliff and conclude the site is broken, when in fact the numbers just became true.

Frequently asked questions

Does GA4 automatically deduplicate purchases by transaction ID?

No. This is a widespread misconception. GA4 sums revenue from every purchase event it receives, so a transaction sent twice contributes its revenue twice. Some reports deduplicate transaction counts, but revenue and event counts do not, which means duplicates corrupt exactly the metrics you optimise against.

Will duplicate purchase events affect Google Ads too?

Yes, if your Google Ads conversions come from the same tag or are imported from GA4. Inflated conversion values feed directly into Smart Bidding, so the algorithm bids more aggressively on segments that appear more profitable than they are. This is the mechanism by which a tracking bug becomes a budget problem.

Can I fix my historical GA4 data after removing the duplicates?

No. GA4 data is immutable once processed and there is no retroactive deduplication. Annotate the date you deployed the fix and treat the periods before and after as separate datasets. For year-on-year comparisons, note the inflation rate you measured so historical figures can be discounted.

Should I use Shopify's native GA4 connection or GTM?

Use the native connection if you want the simplest reliable setup and do not need custom parameters. Use GTM if you need control over event parameters, custom dimensions, consent-aware triggers or server-side routing. Both work well. What causes duplicates is running them together.

About the author

Wahiduzzaman Sohan is a certified Google Ads and GA4 analytics consultant specialising in conversion tracking, server-side tagging and Meta CAPI. He has delivered 100+ tracking implementations for ecommerce and lead-generation brands across the US, UK and Australia, and holds Top Rated Plus status on Upwork.

Full background · LinkedIn · Upwork

Want to know what your duplicates are actually costing you?

A tracking audit quantifies the inflation, finds every source, and hands you a documented fix. Most audits surface the root cause within 48 hours.

Leave a Reply

Your email address will not be published. Required fields are marked *