# GA4 ecommerce events that match real revenue

view_item, add_to_cart, begin_checkout and purchase. Item IDs, tax, shipping and why totals drift.

- Date: 2026-08-21
- Category: Analytics

# GA4 ecommerce events that match real revenue

GA4 ecommerce events only help a store if `purchase` revenue looks like the gateway, not like a dashboard fiction. I am a Gold Coast web developer with 18 years on Magento, Shopify and WooCommerce. I have sat in meetings where conversion rate went up because we double-fired `add_to_cart`, and meetings where it went down because checkout moved and `begin_checkout` died. In 2026 the recommended event names are stable. The implementations are not.

Google's [GA4 ecommerce collection guide](https://developers.google.com/analytics/devguides/collection/ga4/ecommerce) is the schema I implement against. This note is how that schema fails on real catalogues: item IDs that do not match SKUs, tax included twice, shipping omitted, refunds never sent, and thank-you pages that fire on every reload.

If you cannot debug a purchase, you cannot trust a report. Use this with [GA4 DebugView](https://alanvo.com/blog/ga4-debugview-tracking/) and with a dataLayer your theme owns, not only [GTM visual tagging](https://alanvo.com/blog/gtm-visual-tagging-datalayer/).

## Which GA4 ecommerce events a store actually needs

GA4 ecommerce events a store actually needs start with the funnel you will defend, not with every optional event in the spec. I implement these as the spine:

`view_item_list` when a collection or search result renders items you can identify. `select_item` when a card is chosen. `view_item` on the PDP. `add_to_cart` and `remove_from_cart` with the same item shape. `view_cart` if the cart is a real page or drawer you care about. `begin_checkout`. `add_shipping_info` and `add_payment_info` when those steps exist as separate moments. `purchase` once per order. `refund` when the gateway or OMS says so.

That is already a lot. I do not start with `view_promotion` and `add_to_wishlist` unless merchandising lives on those actions. A boutique Shopify theme can wait. A Magento catalogue with layered nav cannot skip `view_item_list` if you want collection performance.

The item object is the contract. `item_id` should be the SKU you use in the warehouse and in the Shopping feed, not a hashed Magento entity ID that changes per store view. `item_name` should be human. `item_brand`, `item_category`, `price`, `quantity` should be honest. `index` on lists helps you see whether anyone clicks row 24.

`purchase` needs `transaction_id`, `value`, `currency`, `items`, and then tax and shipping as their own fields rather than smuggled into value twice. Value should be the order total you agreed with finance: usually revenue including tax or excluding tax, but not both in different weeks. Pick a definition, write it down, and keep Ads conversion value on the same definition if you can.

![GA4 ecommerce events charts on a widescreen monitor](../../images/blog/ga4-ecommerce-events-1.jpg)

Do not send `purchase` with an empty items array. You will get revenue without product reports, and merchandisers will not forgive you.

On [Their Nibs](https://alanvo.com/work/their-nibs/) the published outcome was a storefront rebuild: 31 percent more conversions, 48 percent more orders. That was journey and theme. GA4 still had to record those orders once. On [retail conversion at scale](https://alanvo.com/work/retail-conversion/) Magento work, sessions, funnels and cart abandonment were operational tools. If `begin_checkout` was missing, we would have been arguing about abandonment that was actually a tag gap.

## How GA4 ecommerce events should be shaped in the dataLayer

GA4 ecommerce events should be shaped as a push you own, with `event` and `ecommerce` keys, not as GTM scraping button text. Shopify pixels, WooCommerce plugins and Magento modules all promise a complete dataLayer. I still print the payload on staging.

Clear previous ecommerce data before a new event if you reuse the same object in GTM. Stale items from `view_item` attached to `purchase` is a classic contamination. The current GA4 recommendation is a clean `ecommerce` object per event.

Currency is ISO codes. Do not send `$`. Value is numeric. Quantity is integer. Discount can live on the item. Coupon can live on the purchase. If you run Magento multi-store, do not let store-view currency leak into a property that finance reads in AUD.

User properties and user_id are separate from ecommerce. Logged-in wholesale customers should not share a client_id story with guest retail if you are about to compare conversion rates. That is account design, not an item field.

Consent still wraps this. If `analytics_storage` is denied, you may get no GA4 events or cookieless pings depending on Consent Mode. Do not debug a missing `purchase` for two hours before you check the CMP.

Server-side collection can send the same events via Measurement Protocol or sGTM. The schema does not change. The temptation is to fire both client and server `purchase`. Deduplicate with `transaction_id` and a single pipeline. I expand that in [server-side vs client-side analytics for stores](https://alanvo.com/blog/server-side-vs-client-analytics/).

Theme apps love to inject their own gtag with a different measurement ID. You then have two properties, or duplicates in one. Inventory tags the same way I inventory Ads tags: one owner.

I still write the push in the theme or in a small module I control, even when an app offers a "complete GA4 setup" checkbox. Apps guess at Magento configurable SKUs and at Shopify variant IDs. They often send the parent as `item_id` on `view_item` and the variant on `purchase`. Product reports then look like two catalogues. If I cannot change the app, I overwrite on the thank-you with an order-object pixel and I disable the app's purchase event. Two incomplete events are worse than one complete one.

![Spreadsheet and coffee while reconciling revenue](../../images/blog/ga4-ecommerce-events-2.jpg)

List name and index on `view_item_list` are how you see whether merchandising position 1 actually earns the click. Without them, collection work is a feeling. With them, you can tell a merchandiser that row 1 is ignored on mobile because the filter drawer covers it. That is a storefront ticket, not an analytics philosophy.

## Production checklist for items, tax and purchase

A store's GA4 ecommerce is ready when a test order in DebugView matches the order admin.

1. Measurement ID is production, not a leftover UA-era property, not a duplicate GA4.
2. dataLayer events use the GA4 event names, not Universal Analytics `checkout` steps as the only story.
3. `item_id` matches SKU in admin and feed.
4. `view_item` fires once per PDP load, not on every image swap unless you truly want that.
5. `add_to_cart` fires on success, not on button mousedown before variant validation fails.
6. `begin_checkout` fires when checkout actually starts, including Shop Pay and express wallets if those skip your cart page.
7. `purchase` includes items, tax, shipping, transaction_id, value, currency.
8. Thank-you reload does not send a second `purchase`. Use a flag, a server event, or GTM that keys on transaction_id already seen.
9. Refunds: at least a process for full refunds. Partial refunds if OMS can emit them.
10. Compare seven-day `purchase` revenue in GA4 to gateway, with timezone alignment.

WooCommerce block checkout and Shopify checkout extensibility both move where the thank-you lives. If you still listen for a classic `order-received` body class, you will miss wallet users. Test those paths on purpose.

## What breaks totals until nobody trusts GA4

Duplicates. Theme pixel plus GTM plus an app. Each `purchase` is +1 order in GA4. ROAS looks incredible. Finance calls you a liar.

Missing items. Some connectors send value and transaction_id only. Product reports empty. People then "fix" it by adding a second plugin.

Tax double count. Value includes tax and you also add tax, or Magento display tax confuses the theme developer who copies `grand_total` and `tax_amount` into the wrong fields.

![Analytics graphs on a laptop in a dark office](../../images/blog/ga4-ecommerce-events-3.jpg)

Currency mix. A USD test order in an AUD property. Outliers destroy average order value.

SPA collection pages that never fire `view_item_list` after filter AJAX. Merchandisers think the collection is dead. It is unmeasured.

Consent deny plus no server backup. Safari-heavy fashion traffic looks like a recession.

Item_id as parent product on configurable Magento SKUs. You lose colour and size in reports. You also disagree with Shopping, which advertised the variant.

Refunds ignored. Revenue only goes up. A high-return category looks like a hero.

I fix measurement before I argue merchandising. That was true when I ran Magento retail conversion work and it is true on contract Shopify builds.

Coupon and discount fields are where finance and marketing start separate arguments. If the theme sends `value` after discount and also sends a discount parameter that GTM adds again, AOV looks like a sale that never happened. If you run automatic Shopify discounts plus a code, send the order-level total from the order object, not from a running cart JS variable that missed a second discount. Magento cart rules have the same trap when a developer copies `subtotal` instead of `grand_total` and then "fixes" shipping in GTM by guesswork.

## How to measure whether GA4 matches the gateway

Pick a timezone. Pick a definition of revenue. Pull gateway orders for seven days after conversion lag. Pull GA4 `purchase` event count and total revenue. Pull item quantity for a hero SKU and compare to units sold.

A small gap is consent, blockers, and people who never load the thank-you. A large gap is your tag. A GA4 number above the gateway is almost always duplicates or test traffic.

Use DebugView on a real purchase, then use exploration reports, not only the monetisation overview. Overviews hide parameter mistakes.

When the numbers agree, you can talk about [GA4 campaign benchmarks](https://alanvo.com/blog/ga4-campaign-benchmarks/) and [GA4 attribution](https://alanvo.com/blog/ga4-attribution-ecommerce/) without lying. Until then, benchmarks compare your broken funnel to someone else's.

I also spot-check one SKU through the funnel in an exploration: `view_item` to `add_to_cart` to `purchase` item quantity. If views are huge and purchases exist but add-to-cart is almost zero, the add event is tied to the wrong button (wishlist, notify me, or a size guide). If add-to-cart is huge and purchase items are zero, checkout is untagged or items were dropped from the purchase payload. That SKU walk finds bugs faster than a property-wide conversion rate.

![Team looking at a dashboard on a wall screen](../../images/blog/ga4-ecommerce-events-4.jpg)

## Related work on this site

[Retail conversion at scale](https://alanvo.com/work/retail-conversion/) is three Magento stores, funnels and a $2.5 million year-on-year sales increase over three years. Measurement was part of the job. [Their Nibs](https://alanvo.com/work/their-nibs/) is a Shopify rebuild with published conversion and order lifts. GA4 ecommerce events sit under both: one order, one `purchase`, items that merchandisers recognise. For the debug pass, use [GA4 DebugView](https://alanvo.com/blog/ga4-debugview-tracking/). For how the payload gets into GTM, use [visual tagging vs dataLayer events](https://alanvo.com/blog/gtm-visual-tagging-datalayer/).

## FAQ

### Which GA4 ecommerce events are mandatory?

None are mandatory in the product sense, but a store that cannot send a correct `purchase` should not be making channel decisions from GA4. I treat `view_item`, `add_to_cart`, `begin_checkout` and `purchase` as the minimum honest funnel.

### Why do GA4 ecommerce events show more revenue than Shopify or Magento?

Duplicates, tax included twice, or test orders. DebugView the thank-you. Count how many `purchase` events you see for one order. Then check value against the order admin.

### Can GA4 ecommerce events use product name as item_id?

You can, and you will regret it when names change. Use the SKU. Names belong in `item_name`.

### Do GA4 ecommerce events replace Google Ads conversion tags?

No. They can share a dataLayer. Ads still needs its own conversion action, preferably with the same transaction_id and value rules. Keep them aligned, not merged into one vague "pixel."


![Phone and laptop showing the same store metrics](../../images/blog/ga4-ecommerce-events-5.jpg)


HTML version: https://alanvo.com/blog/ga4-ecommerce-events/
