GA4 Purchase Counted Twice? How to Stop Duplicate Purchases

Quick fix: Make sure every purchase event sends a unique, non-empty transaction_id that matches your real order number, and that only one tag sends the purchase. Then stop the event from firing again when the thank-you page is reloaded or revisited.

GA4 shows more purchases than your store actually received. Or revenue in GA4 is suspiciously close to double what your payment provider reports. Duplicate purchase events are one of the most common ecommerce tracking problems, and they matter: inflated revenue misleads reporting, and if you import GA4 key events into Google Ads, it misleads bidding too.

GA4 has a built-in safety net for this, but it only works when your tagging gives it what it needs. This guide explains how that safety net works, what usually defeats it, and how to fix the cause instead of relying on it.

How GA4 deduplicates purchases

Google's transaction ID documentation states that Google Analytics deduplicates purchase events that have the same transaction ID. In practice that means if the same order is sent twice with the same transaction_id, GA4 should count it once.

There are important limits:

  • Web streams only. Google says transaction ID deduplication only works for data collected through web streams, not app streams.
  • Same user. The documentation describes deduplicating transactions from the same user, and says the same transaction ID should not be used across different users.
  • The ID must be identical. 1001 and #1001 are different values, so they are not deduplicated.
  • Empty strings are a trap. GA4 deduplicates all purchase events where transaction_id is an empty string, which can make many separate orders collapse into one.

Google does not document a time window for deduplication. The safest approach is to treat deduplication as a backstop, and make sure each order only fires one purchase event in the first place.

Step 1: Confirm you really have duplicates

Before changing anything, find out what kind of duplication you have. In GA4, go to Explore and create a free-form exploration:

  1. Add the dimension Transaction ID and the metrics Ecommerce purchases and Purchase revenue.
  2. Put Transaction ID in rows and the two metrics in values.
  3. Sort by Ecommerce purchases, descending.

Now read the results:

  • IDs with a count above 1: the same order arrived more than once and was not deduplicated. This points to app data, different users or devices sending the same ID, or events GA4 could not match as the same user.
  • A large row for (not set) or an empty ID: purchases are being sent without a usable transaction ID.
  • Every ID shows 1, but the total is still too high: the same order is being sent with different IDs, or there are extra orders that do not exist, such as test orders.

Compare a handful of transaction IDs with your store's order list. This tells you whether GA4 is inventing orders or just counting real ones more than once.

Step 2: Stop thank-you page reloads from firing purchase again

This is the most common cause. The purchase event fires every time the order confirmation page loads. That page gets loaded again more often than you might think:

  • The customer refreshes the page, or the browser restores the tab later.
  • The customer uses the back button and returns to it.
  • The confirmation page doubles as an order status page linked from the confirmation email.
  • The customer opens the page on another device while logged in.

If every load sends the same transaction_id from the same browser, GA4's deduplication should catch it on web. But an order status page opened from an email on a phone is a different device, and that is exactly the case deduplication is not designed for.

The reliable fix is to send the purchase only once per order. The best place to do this is on your server: record when an order's purchase data has been output, and do not output it again. If you cannot change the server, add a browser-side guard before pushing to the data layer:

(function () {
  var orderId = '{{ORDER_ID}}'; // filled in by your platform
  var key = 'ga4_purchase_sent_' + orderId;
  try {
    if (window.localStorage.getItem(key)) return;
    window.localStorage.setItem(key, '1');
  } catch (e) { /* storage blocked: fall through and rely on transaction_id */ }

  window.dataLayer = window.dataLayer || [];
  window.dataLayer.push({ ecommerce: null });
  window.dataLayer.push({
    event: 'purchase',
    ecommerce: {
      transaction_id: orderId,
      value: 59.90,
      currency: 'USD',
      items: [ /* item objects */ ]
    }
  });
})();

A browser guard only prevents repeats in the same browser, which is why a server-side flag is better. Either way, keep a real transaction_id on the event so GA4 can still deduplicate anything that slips through.

The ecommerce: null push clears the previous ecommerce object in the data layer, so values from an earlier event are not merged into the purchase. Replace the example values with your platform's real order data.

Step 3: Remove extra tags that send purchase

The second most common cause is two separate tags sending the same purchase. Typical combinations:

  • A platform integration (such as an ecommerce app or plugin) sends purchase, and a GTM tag sends it too.
  • A hard-coded gtag.js snippet on the thank-you page fires purchase alongside GTM.
  • A GTM GA4 event tag has two triggers, for example a custom purchase event and a page view on the confirmation URL, so it fires twice on one load.
  • Purchases are sent both from the browser and from your server with the Measurement Protocol.

If both tags send the same transaction_id for the same user, GA4 should deduplicate them. But the tags often format the ID differently, send it from different client IDs, or one of them sends no ID at all, and then both count.

To find every source, open the confirmation page with your browser's Network tab filtered to collect and count the requests where en=purchase. Check the transaction ID value in each. The Pixel Helper extension is another quick way to see every event fired on the confirmation page. For a full walkthrough of finding hidden installations, see our guide to duplicate gtag.js and GTM tags.

Choose one source of truth for purchase events and disable the rest. If you also run server-side purchase tracking for other platforms, our article on Meta Pixel and Conversions API deduplication explains the same principle for Meta.

Step 4: Fix the transaction ID itself

Even with a single tag, a bad transaction ID breaks deduplication or causes undercounting. Check that your transaction_id:

  • Is never empty. If your platform variable is undefined on some pages, the tag may send an empty string, and GA4 will deduplicate all of those purchases together.
  • Is unique per order. Google warns that reusing the same ID across transactions can significantly undercount key events. A hard-coded test value left in a tag is a common culprit.
  • Is consistent everywhere. Use the same format in every tag and in any server-side events, ideally your order number exactly as it appears in your store.
  • Contains no personal data. Google says transaction IDs must not contain information that could identify a customer, such as an email address.

If you import GA4 key events into Google Ads, or run a separate Google Ads conversion tag, duplicates can show up there too. Google Ads has its own transaction ID deduplication: when two conversions for the same conversion action share a transaction ID, the second is treated as a duplicate and not counted. Send the same order ID to Google Ads as well. If conversions are missing rather than duplicated, see our Google Ads conversion troubleshooting guide.

How to verify the fix

  1. Enable debug mode, for example by connecting with Tag Assistant, and place a test order.
  2. In GA4 DebugView, confirm exactly one purchase event appears, and click it to check transaction_id, value, currency and items.
  3. Reload the confirmation page twice. No new purchase event should appear in DebugView or in the Network tab.
  4. Open the order status link from the confirmation email, ideally on another device. It should not send a purchase.
  5. After about 24 hours, when purchase data appears in standard reports, repeat the Transaction ID exploration and compare totals with your store's orders for the same days.

Exclude or refund test orders. A test purchase with real values is counted like any other order and will skew revenue reports if you leave it in.

Historical duplicates will not be removed from GA4 reports. Record the date of the fix so you can explain the change in trend lines later.

FAQ

Does GA4 always remove duplicate purchases with the same transaction ID?

Only for web stream data, and Google describes it as deduplicating transactions from the same user. App streams are not deduplicated, and duplicates sent from different users or devices may still be counted. Fix the cause rather than relying on it.

Why do I see a large "(not set)" row for Transaction ID?

Purchase events are arriving without a transaction ID. Check that your tag reads the order number from the data layer on the confirmation page and that the variable is available when the tag fires.

Why does GA4 show fewer purchases than my store?

That is a different problem: consent choices, ad blockers, and customers who close the page before the tag loads all reduce GA4's count. Reused or empty transaction IDs can also collapse separate orders. See GA4 events not showing for the other causes.

Should I send purchases from my server instead?

Server-side events are less affected by browser blocking, but sending purchases from both browser and server is a common source of duplicates. If you do both, use the exact same transaction ID and test carefully, or pick one source.