GA4 ecommerce events: the required parameters nobody validates
Google marks currency and value required with an asterisk, then reports nothing when they're absent. What actually breaks, and the checks that catch it.
Short answer: every purchase event needs transaction_id, value, currency and a populated items array, and every item inside it needs item_id or item_name — those are the fields Google's own reference marks as required (Google Analytics for Developers, checked 11 August 2026). Send one without currency and your revenue metrics stop being computed correctly, with no error anywhere in the interface: Google's remedy for absent revenue is a checklist you run against your own tag. The contrast worth holding onto is Google Ads, which on a duplicate conversion tells you outright — "You'll get an error message, and the duplicate conversion won't be counted" (Google Ads Help, checked 11 August 2026). GA4 just hands you a number that looks plausible.
Google's "required" column has an asterisk, and the asterisk is the whole story
The ecommerce spec looks strict until you read the footnotes, at which point "required" turns out to mean three different things.
In Google's reference tables, items on a purchase is a plain Yes. transaction_id is a plain Yes. But currency and value are both Yes*, and the asterisks explain themselves: "value is typically required for meaningful reporting" and "currency is required if you set value" (Google Analytics for Developers, checked 11 August 2026). At item level, item_id and item_name are each Yes* with the note "One of item_id or item_name is required."
So the real contract is conditional. Nothing rejects an event that breaks it.
| Event | Required | Required at item level |
|---|---|---|
purchase | transaction_id, items, value*, currency* | item_id or item_name |
refund | transaction_id, value*, currency* (items optional) | item_id or item_name |
add_to_cart, begin_checkout, add_payment_info, add_shipping_info, view_item, view_cart | items, value*, currency* | item_id or item_name |
view_item_list | items, currency* (no value) | item_id or item_name |
select_item | items | item_id or item_name |
* Conditionally required per Google's footnotes: set value and currency becomes required with it.
Two details in that table catch people out. refund doesn't need an items array, so a partial refund and a full one look identical unless you send items deliberately. And view_item carries a warning of its own in the same table: value metrics on view_item don't contribute to revenue, so a large number there is not revenue you're about to bank.
A missing currency breaks revenue, and nothing in the interface says so
This is the failure that survives longest, because everything else about the event looks healthy.
Google is specific about what builds the revenue figures: Analytics "uses the value and currency parameters from the purchase event to calculate event-level revenue metrics", and "the price and currency parameters from the purchase event to calculate item-level revenue metrics" (Analytics Help, checked 11 August 2026). The reference states the dependency the other way round — set value and "currency is required for revenue metrics to be computed accurately."
What Google doesn't publish is an error surface. The page you land on when your revenue is absent is called "Fix missing revenue data", and every instruction on it is a check you perform on your own code: confirm value is "assigned a number rather than a string (and does not contain quotation marks)", that it's "assigned at the event level of the purchase event", that it doesn't "contain a currency symbol", and that currency is "assigned a string rather than a number using the 3-letter ISO 4217 format" (Analytics Help, checked 11 August 2026). That's a troubleshooting method for a system that doesn't tell you it's broken.
We went looking for a Google page stating what GA4 reports when currency is absent — zero, blank, or the property default — and didn't find one that says either way. So don't take the popular "it silently zeroes your revenue" formulation on trust, including from us. What is documented is the dependency and the absence of any alert. The practical read is the same: the purchase count can be perfect while the money is wrong, and you'll only learn it by comparing GA4 against the order table yourself.
The $100.00 case is worth saying out loud because it's so easy to ship. A value of "$100.00" is a string containing a symbol, which fails two of Google's four checks at once, and the event still fires cleanly.
An empty transaction_id collapses every purchase you have
transaction_id is what stops a confirmation-page refresh counting twice, and its failure modes are stranger than the parameter deserves.
The rule is plain: "By adding a transaction ID, Google Analytics will deduplicate purchases if two purchases are registered with the same transaction ID" (Analytics Help, checked 11 August 2026). A customer who reloads the thank-you page fires the purchase event again with the same order number, and the second one is discarded. Without the ID, you have two purchases and double the revenue.
Then the note underneath, which is the one to design around: "Don't send an empty string as the transaction ID. Google Analytics will deduplicate all purchase events that have transaction_id=''." Read that as a failure mode rather than a warning. If your template resolves the order number to an empty string — a race with the order object, a variable that didn't populate on a client-side checkout, a fallback of '' — GA4 doesn't drop those events for being malformed. It treats them as repeats of each other. Every affected purchase across the whole period collapses into one. The tag fires, DebugView shows the event, and your revenue quietly falls off a cliff.
The reverse mistake is documented too: reuse one ID across different transactions and "you could significantly undercount your key events" (Analytics Help, checked 11 August 2026).
One limit that isn't widely known: "Transaction ID deduplication only works for data collected through web streams, not app streams" (Analytics Help, checked 11 August 2026). If you're sending purchases from an app, the deduplication you're relying on isn't running.
Google Ads tells you about a duplicate; GA4 doesn't
Worth putting side by side, because the same order number does different work in each system.
Google Ads describes its own behaviour like this: "If there are 2 conversions for the same conversion action with the same transaction ID, Google Ads will know the second conversion is a duplicate", and then "You'll get an error message, and the duplicate conversion won't be counted" (Google Ads Help, checked 11 August 2026). It also notes that "Transaction IDs won't be reported in Google Ads", so the ID is a control input, not something you can inspect afterwards.
An error message is a gift. It means the platform disagrees with your implementation out loud. GA4's deduplication is silent in both directions — a genuine duplicate vanishes without comment, and so does the mass collapse caused by an empty ID.
This matters more than a tidy dashboard, because the value on those events is what downstream bidding is being trained on. A revenue figure that's wrong in a stable, consistent way is worse than one that's obviously broken: nobody investigates a number that looks reasonable.
Multi-currency revenue is converted at yesterday's rate
If you sell in more than one currency, GA4's revenue will never tie exactly to your finance system, and the reason is documented rather than mysterious.
Businesses transacting in several currencies "can specify a local currency type when sending transaction data to Analytics", and "Analytics will perform the necessary conversion using the prior day's exchange rate" — data "is based on the exchange rate one day before the transaction, and is provided by financial exchanges" (Analytics Help, checked 11 August 2026).
Your payment provider settles at its own rate on its own timing. GA4 uses yesterday's. Both are right, and they won't agree. Send the local currency per transaction, keep the property's reporting currency stable, and stop trying to reconcile the last two per cent — reconcile against the order table in your own database instead, which is a job for the raw export rather than the interface.
Before you debug parameters, check the event is arriving at all. With analytics_storage set to denied, the Google tag will "not read or write first-party analytics cookies" and sends cookieless pings instead (Analytics Help, checked 11 August 2026) — and a tag blocked by the CMP sends nothing whatsoever. Our free cookie and tracker scanner loads your site in a real browser and reports which tags fire before and after consent, plus which CMP is enforcing, so you can tell a payload problem from a consent problem before you go looking for the wrong bug.
The five checks that catch most of it
Run these against a real order rather than a test page, because the failures cluster around values that only exist at checkout.
- Fire a purchase, then reload the confirmation page. Two purchase events in DebugView with the same
transaction_idis correct behaviour. Two different IDs for one order is the bug. - Check
transaction_idis never empty. Assert on it in your dataLayer push and fail loudly rather than falling back to''. - Read the raw payload, not the report. Confirm
valueis a number with no symbol andcurrencyis a three-letter code, at event level. - Confirm every item has
item_idoritem_name. One of the two, on every element of the array — not just the first. - Reconcile one day of GA4 purchases against your order table. Count and revenue. If the count matches and the money doesn't, look at
currencyandvaluefirst.
What to do with a revenue number you don't trust
Start from the order table, not from GA4. Pick one day, count the orders and total the money, then compare both against the purchase events. A matching count with mismatched revenue points at value and currency. A short count points at deduplication, most likely an ID that's empty or repeated. A long count points at a tag firing more than once per order.
Whichever it is, the fix belongs in the layer that builds the payload, not in GA4's settings. Getting the data that reaches Google to match what actually happened — through the consent layer, the tagging layer and into bidding — is the work we do.
Sources
- 1.Google Analytics for Developers — Recommended events (ecommerce parameter tables) · Checked 2026-08-11
- 2.Analytics Help — [GA4] Minimize duplicate key events with transaction IDs · Checked 2026-08-11
- 3.Analytics Help — [GA4] Fix missing revenue data · Checked 2026-08-11
- 4.Analytics Help — Currency reference (conversion at the prior day's rate) · Checked 2026-08-11
- 5.Google Ads Help — Use a transaction ID to minimize duplicate conversions · Checked 2026-08-11
- 6.Google Analytics for Developers — Measure ecommerce · Checked 2026-08-11
- 7.Analytics Help — Consent mode behaviour when analytics_storage is denied · Checked 2026-08-11