"I set up GA4 ecommerce, but the revenue in my Monetization report doesn't match my cart." This is one of the most common headaches in online-store measurement. Purchases are happening, yet the amount is missing, or not a single item was recorded. Most of the time the cause comes down to a flaw inside the purchase event sent at checkout.
Does any of this sound familiar?
- The Monetization report shows purchases but zero (or blank) revenue (
value) - "Ecommerce purchases" appears, but the
itemslist is empty - Your cart dashboard and GA4 disagree on the revenue amount
- The same order is counted twice, inflating revenue above reality
This guide organizes how to verify your GA4 purchase event is sent correctly, parameter by parameter. From a practical angle: where to look to isolate "sent vs. not sent."
First, the parameters a purchase event needs
The center of GA4 ecommerce measurement is the recommended purchase event, sent when a purchase completes. To record revenue and products correctly, these parameters must all be present.
| Parameter | Role | Common flaw |
|---|---|---|
transaction_id |
Uniquely identifies the order | Missing → the same purchase is double-counted |
value |
Total purchase amount | Unset / sent as a string → revenue shows 0 |
currency |
Currency code (ISO 4217) | Not JPY/USD, or missing → the amount isn't aggregated |
items |
Array of purchased products | Empty array or wrong schema → products aren't recorded |
The key point: value and currency always go together. Without a currency, GA4 can't aggregate the amount as revenue. And items is an array where each product carries fields like item_id and item_name. If that array is empty or its structure is wrong, the purchase is still counted, but the products inside it disappear.
The items array gotcha (products not recorded)
When "purchases appear but products are empty," it's almost always an items problem. Three typical causes:
- Empty items array:
purchaseis sent, butitems: []. The cart data is getting cut off somewhere in the handoff. - Wrong schema: each product has neither
item_idnoritem_name. GA4 requires at least one of the two, so if both are missing the entry isn't treated as a product. - Wrong field names: you're still sending old Universal Analytics keys like
idorname. GA4 expectsitem_id/item_name.
Common pitfall: if you can see the
purchaserequest fire in DevTools but no products are recorded, check the request body one item at a time — isitemsempty, and are the keysitem_id/item_name? "The event is firing" does not mean "the products came through correctly."
The value / currency gotcha (revenue doesn't match)
Flaws around the amount quietly break your revenue reports.
valuesent as a string: sending"3000"as a string can prevent GA4 from aggregating it as a number, so it never reaches revenue. Pass a number (3000).currencymissing or non-ISO: ifcurrencyis absent, or written as¥oryen, it won't aggregate. It must be the three-letter ISO 4217 code (JPY,USD, etc.).- Inconsistent tax/shipping handling: whether
valueincludes tax and shipping must match between your cart and GA4, or it surfaces as an amount discrepancy. Either choice is fine — being consistent is what matters.
Common pitfall: if "purchase counts match but only revenue is off," suspect what you're including in
valuefirst (tax-in vs. tax-out, shipping or not). Most discrepancies live here.
The transaction_id gotcha (double-counting)
transaction_id is the parameter that uniquely identifies an order. If it's missing, or changes every time, GA4 treats the same purchase as separate ones and counts them more than once.
- Every time the thank-you page is reloaded,
purchasefires again — and without atransaction_id, each reload is counted as new revenue - Conversely, with a correct
transaction_id, GA4 de-duplicates repeat purchases sharing the same ID
For a fuller breakdown of double-counting itself, see Why conversions get counted twice, and how to fix it. The basic rule: put a value that is unique per order and never changes — like the order number — into transaction_id.
dataLayer pitfalls (GTM implementations)
If you use Google Tag Manager to send the purchase event, the data flows through the dataLayer. Three issues here account for a large share of "purchase fires but data is wrong" tickets.
- Not clearing the ecommerce object before each push. GTM merges dataLayer pushes recursively. If a previous ecommerce event (e.g.
add_to_cart) left product data in theecommerceobject, that stale data bleeds into yourpurchasepush. The fix: always push{ ecommerce: null }immediately before your purchase push. - Trigger timing vs. dataLayer availability. If your GA4 Event tag fires on DOM Ready but the cart platform pushes the transaction data to the dataLayer slightly later, the tag fires with an empty
ecommerceobject. Check the order in GTM Preview mode: thepurchasedataLayer push must appear before the tag fires. - Using the wrong variable type. GTM has a built-in "Data Layer Variable" for ecommerce fields (e.g.
ecommerce.transaction_id). If you accidentally create a custom JavaScript variable or hard-code the value, the mapping can silently break when the cart format changes.
Quick test: open GTM Preview, complete a test purchase, and look at the Variables tab for the
purchaseevent. Every ecommerce field (transaction_id,value,currency,items) should be populated. If any isundefined, the dataLayer push is either missing it or firing after the tag. For broader GTM troubleshooting, see Why your GTM tag isn't tracking.
Payment gateway redirects and purchase-event loss
When checkout redirects the buyer to an external payment gateway (PayPal, Stripe Checkout, Klarna, etc.) and then back to your thank-you page, the purchase event is at high risk of never firing or losing its data.
- Session break on return. The redirect creates a new navigation. If the browser treats the return as a new session (common with Safari ITP and ad-blocker heuristics), the thank-you page may load without the cart context needed to populate
itemsorvalue. For more on Safari-specific data loss, see Safari ITP and conversion loss. - Attribution overwrite. Even when
purchasefires, GA4 may attribute the conversion to the payment domain as a "referral" source instead of the original traffic source. Add the payment gateway domain to your cross-domain link list in the GA4 data stream settings to preserve the session. See Why cross-domain tracking breaks for the full setup. - Server-side fallback. For gateways that reliably send a server-side webhook (e.g. Stripe
checkout.session.completed), consider firing thepurchaseevent via the GA4 Measurement Protocol on your server instead of relying on the client-side script. This avoids the redirect problem entirely. For the server-side approach, see Server-side GTM verification.
The ecommerce event sequence matters
GA4 ecommerce events follow a funnel: view_item → add_to_cart → begin_checkout → purchase. While purchase can fire independently, breaking an upstream event can mask problems that eventually surface in purchase data.
- If
add_to_cartnever populates the dataLayerecommerce.itemscorrectly, the same malformed items array is likely carried through topurchase. - Some cart platforms build the
itemsarray progressively across funnel steps. Ifbegin_checkoutis skipped or fires with an error, the array handed topurchasemay be incomplete. - Verifying the full funnel — not just the final
purchase— catches schema errors earlier and makes debugging faster.
Four ways to confirm it's being sent
You can confirm the purchase event and its contents are sent correctly, in this order:
- GA4 DebugView: enable debug mode, run a real (or test) purchase, and in "Admin → DebugView" watch for the
purchaseevent and whethervalue,currency, anditemsare present. The key is to expand items and check inside. - DevTools Network: in your browser's DevTools, filter for
collectand confirm a request to…/g/collectfires when the thank-you page loads. In the body, inspecten=purchase,transaction_id,value,currency, anditems. - GA4 Monetization report: in "Reports → Monetization → Ecommerce purchases," confirm per-product quantity and revenue are accumulating (standard reports have a processing delay).
- Check duplicates by transaction_id: in an exploration report, verify the same order isn't counted twice by looking for duplicates at the
transaction_idlevel.
To confirm whether the GA4 tag itself is firing at all, read How to verify your GA4 tag is working first — it makes isolation faster.
What checking on your own machine can't tell you
DebugView and DevTools are very useful, but running one test purchase on your own machine has two structural limits.
- Your environment isn't your visitors' environment. Login state, extensions, office-IP exclusion, and ad blockers mean
purchasecan fire correctly on your machine yet be missing under real buyer conditions. Especially in flows that pass through a payment gateway or redirects, the session can drop before returning to the thank-you page, leavingitemsempty. - Revenue involving an ad click is hard to verify. To properly see "does an ad-driven purchase really get recorded as revenue," you'd need to click a live ad to recreate the path — which carries the risk of an ad-policy violation as a self-click.
Judging "it showed up in DebugView, so it's fine" tends to miss the problem of value or items going missing on the real checkout flow your buyers take. For the mindset of going past firing to whether it's actually recorded, see Why it fires but isn't recorded.
Verification checklist
- Does the
purchaseevent fire on the thank-you (purchase-complete) page? - Is
valuea number (not a string)? - Is
currencyan ISO 4217 code (JPY/USD, etc.)? - Is the
itemsarray non-empty, withitem_id/item_nameon each product? - Is
transaction_idpresent and unique per order (to prevent double-counting)? - Is whether tax/shipping is included in
valuealigned with the cart side? - Is the same order not counted twice (check at the
transaction_idlevel)? - Is it also not missing under real user conditions in production (don't judge from one run on your machine)?
Frequently asked questions
Q. Purchases appear, but revenue (value) is zero. Why?
A. Typically value is unset or sent as a string, or currency is missing/non-ISO so it can't aggregate. Check the value and currency contents in the …/g/collect request in DevTools Network.
Q. Why are only the items not recorded?
A. Most likely the items array is empty, or each product lacks item_id / item_name. Also check you aren't still sending the old UA id / name keys.
Q. Why is the same purchase counted twice?
A. Typically transaction_id is missing, or it changes on every reload. Put a unique, unchanging value like the order number in it and GA4 will de-duplicate. See the duplicate-tracking article for details.
Q. Should tax and shipping be included in value?
A. GA4 has no hard rule, but the most important thing is to align the cart's revenue definition with GA4's value. A mismatch always surfaces as an amount discrepancy.
Q. If I can see purchase in DebugView, am I safe? A. That confirms only that it was sent. You're safe only once you've confirmed it accumulates correctly in the standard Monetization report — and that it isn't missing on your buyers' real checkout flow in production.
Conclusion: don't stop at firing — check the contents and production recording
GA4 ecommerce purchase measurement only works correctly once the purchase event has transaction_id, value, currency, and items all in place. DebugView and DevTools confirm it's "being sent," but what matters is what comes after — whether the amount and products are recorded without loss, on the real checkout flow your buyers take, in production.
ConversionOK runs your live page in an independent, isolated browser and intercepts the purchase event actually sent (including value, currency, and items), then verifies it. No need to click your own ad, and no interference from your office environment. Start with a free static check to confirm the entry point of your ecommerce measurement.