Duplicate purchases in GA4 - why they happen and how the auditor finds them
In the GA4 e-commerce reports, double the revenue suddenly appears for the
same order. Three weeks later the reporting team asks why the order count
does not match the shop backend. Duplicate purchase events are a classic
GTM bug - and one that the auditor can find in under a minute.
What happens in the code
The check in app/queries/checks/duplicate_events.sql
groups events into second buckets and marks events fired multiple times
within the same session:
duplicates_marked AS (
SELECT *,
COUNT(*) OVER (
PARTITION BY session_id, user_pseudo_id, event_name, second_bucket
) AS duplicate_count,
ROW_NUMBER() OVER (
PARTITION BY session_id, user_pseudo_id, event_name, second_bucket
ORDER BY event_timestamp_micros ASC
) AS occurrence_number
FROM events_with_bucket
)
The bucket calculation happens beforehand via
TIMESTAMP_SECONDS(event_timestamp_micros / 1000000). Multiple
purchase events within the same second, the same session and the same
user_pseudo_id are marked as a duplicate.
Thresholds
| Status | Condition |
|---|---|
| Green | 0 duplicates |
| Yellow | 1-5 duplicates |
| Red | >5 duplicates or >5 % of purchases |
Typical causes
- Double-click on the checkout button - the user clicks twice quickly, both clicks trigger an Ajax call within the same second.
- GTM trigger fires multiple times - the
purchasetrigger is not set toOnce per Page, or a history-change listener fires it again on top. - Plugin conflict - an e-commerce plugin (e.g. the WooCommerce GA4 plugin) sends the purchase in parallel with a custom GTM tag.
- Browser retry - on network latency the browser repeats the request, the server processes both calls.
- Incomplete Universal Analytics migration - the old UA snippet still runs alongside the new GA4 tag.
How the auditor shows it
The create_duplicate_transactions_section section in
app/components/dashboard/sections/ecommerce_transactions.py
delivers:
- Warning with count and revenue impact - how many purchases are duplicated, how much revenue depends on it?
- Summary cards for the duplicate count and affected transaction IDs.
- Detail table with concrete
transaction_id,event_timestamp,purchase_revenueand the number of repetitions.
This lets you narrow down not only the whether, but also the where - often duplicates trace back to a few trigger configurations.
How to prevent them
- Use
transaction_idas the dedup key in GA4. GA4 itself deduplicatespurchaseevents with an identicaltransaction_idfor a limited time, but not reliably - so you should fix the bug at the source. - Set the GTM trigger to
Once per Pageand make sure no parallel history-change listener fires along with it. - Plugin audit - if your shop has a dedicated e-commerce plugin, check whether it sends purchases in parallel with your custom tracking.
- Idempotency in server-side tagging - if your SST setup reacts to
retries, make sure identical
transaction_idcalls within, say, 60 seconds are discarded. - In the auditor, check again after 24 h under E-commerce checks.
Related topics
- Help page E-commerce checks in detail
- Help page Event quality & PII hints
- Blog When the
gclidis there - and Google Ads still is not
Frequently asked questions
- How do I find duplicate transactions in GA4?
- The GA4 Auditor groups purchase events from the BigQuery export into second buckets per session and user_pseudo_id and marks multiple firings. This shows you the count, the affected transaction_id and the revenue impact - instead of just a suspicious total number.
- Why do duplicate purchase events happen?
- The most common causes: double-click on the checkout button, a GTM trigger without 'Once per Page', e-commerce plugins sending in parallel, browser retries on latency and an incomplete migration from Universal Analytics where the old snippet still runs alongside.
- Does GA4 deduplicate based on the transaction_id?
- GA4 deduplicates purchase events with an identical transaction_id for a limited time, but not reliably. You should fix the bug at the source - set the GTM trigger to 'Once per Page', resolve plugin conflicts and react idempotently to retries in server-side tagging.