Your GA4 purchase event fires perfectly in GTM Preview mode. You see it in the debug panel with all parameters populated. You publish the container. Two weeks later, someone checks the Monetization → E-commerce purchases report and finds 40% fewer transactions than Shopify or your payment processor reports.
GA4 purchase events fail silently because GA4 does not validate the data you send. It accepts malformed events without error. It drops events that hit rate limits without warning. It records events with missing required parameters but excludes them from commerce reports. The event exists in the raw data stream but never appears in the reports you actually look at.
GA4 requires the currency parameter on every purchase event. It must be a valid ISO 4217 code: USD, INR, EUR. If you send $, Rs, usd (lowercase), or an empty string, GA4 accepts the event but excludes it from all monetary reports. The event appears in DebugView. It never appears in Revenue metrics.
// WRONG - will be excluded from revenue reports
dataLayer.push({
event: 'purchase',
ecommerce: {
currency: 'Rs', // Not a valid ISO 4217 code
value: 4999
}
});
// CORRECT
dataLayer.push({
event: 'purchase',
ecommerce: {
currency: 'INR',
value: 4999
}
});
The items array is required for purchase events to appear in e-commerce reports. If the array is empty, null, or missing, the purchase event fires successfully but does not populate product-level reports. Common cause: the data layer push happens before the cart data is available, so items resolves to an empty array.
GA4 deduplicates purchase events by transaction_id. If a user refreshes the thank-you page, the purchase event fires again with the same transaction ID. GA4 drops the duplicate. This is correct behaviour. But if your transaction ID generation is broken — returning the same ID for different transactions, or returning undefined (which GA4 treats as a single shared ID) — GA4 silently deduplicates real, distinct transactions.
If analytics_storage is denied when the purchase event fires, GA4 sends a cookieless ping. This ping does not include the full event payload. Purchase data from these pings may not appear in e-commerce reports. Depending on your consent implementation timing, a meaningful percentage of purchases may fire in the denied state.
On platforms like Shopify, the order confirmation page loads asynchronously. The GTM trigger fires on page load. But the order data (transaction ID, value, items) is injected into the data layer via a script that loads after the page. Your tag reads the data layer, finds the values are not yet present, and sends a purchase event with value: undefined and transaction_id: undefined.
This is especially common on headless commerce platforms where the checkout is an iframe or a third-party hosted page. The data layer push timing is unpredictable.
If your checkout is on a different domain (e.g., checkout.shopify.com), the GA4 client ID does not carry over unless cross-domain tracking is configured. The purchase event fires with a new client ID. GA4 records the purchase but cannot attribute it to the original session. It appears in raw event data but attribution reports show it as a direct/none conversion.
GA4 has undocumented rate limits on event collection. During flash sales or high-traffic events, if your site sends more than approximately 500 events per second from a single client, GA4 begins sampling. Purchase events during these peak periods may be dropped entirely. You will never see an error — the events simply do not arrive.
Compare three data sources daily:
If the tag monitor shows 100 purchase fires but GA4 shows 80, the 20 missing events were rejected by GA4 (malformed data, rate limits). If the payment processor shows 120 transactions but the tag monitor shows 100, the 20 missing events never fired in the browser (consent block, page load failure, tag error).
This three-way comparison isolates the failure point. Without it, you are guessing.
Across every tag, every page, 24/7. Set it up in 5 minutes. No GTM dependency. No developer required.
Start 14-day free trial →Across every tag, every page, 24/7. Set it up in 5 minutes.
No GTM dependency. No developer required.