HexCorp / Guides / Tracking & analytics
Problem fix
Why GA4 revenue never matches WooCommerce
GA4 under-reports WooCommerce revenue because it only counts purchases a browser managed to report, and on most stores a quarter of browsers never do: declined consent, ad blockers, thank-you pages that never load, and duplicates and currency rules make up the rest. The fix is to reconcile order IDs instead of totals, send the purchase from your server as well as the browser, and set an alarm on the match rate.
What “normal” looks like
After eighteen years of reconciling analytics against order systems, the pattern is consistent. A store with a well-built setup and a European or privacy-conscious audience lands at 85 to 92 percent of WooCommerce revenue showing in GA4. A store that has never checked sits at 60 to 80 percent. Anything under 60 percent means something is broken, not leaky. And a store reporting more revenue in GA4 than WooCommerce has a duplicate problem, which is worse than a gap because it makes every ad platform think it is doing better than it is.
So the first question is not “why don’t they match”, because they never will. It is “which of the seven reasons below is eating my orders, and in what proportion”.
The seven reasons, in the order they usually matter
1. Consent was declined or never answered
With a cookie banner in Consent Mode, GA4 does not set its cookie until the visitor accepts. If they decline, or buy before answering, the purchase goes to GA4 as a cookieless ping at best. Google models some of that traffic back, but modelled conversions appear days later and never with a transaction ID. On a store whose customers are mostly in the EU or UK, declined consent alone can account for a 20 to 35 percent gap.
2. An ad blocker or privacy browser ate the request
Brave, Firefox with strict protection, iOS Safari with Private Relay and every uBlock Origin install block googletagmanager.com or google-analytics.com outright. In a tech-literate audience that is a quarter of visitors; in a general consumer audience closer to 10 percent. The order still goes through. GA4 never hears about it.
3. The thank-you page never loaded, or loaded without the event
This is the one people underestimate. The purchase event fires on the order-received page. If the customer pays through PayPal, Klarna or a bank redirect and closes the tab at the “returning you to the store” screen, the page never loads. If they are on a slow connection and tap the back button, the script never runs. If your theme renders the order-received page from a cached copy, the event fires with empty data. Offsite payment methods alone typically lose 3 to 8 percent of orders this way.
4. The event fired twice
Customers refresh the thank-you page. They bookmark it. They come back from the email receipt. Unless your tracking dedupes on transaction_id, each visit is another purchase in GA4. A single tracking plugin plus a hand-pasted gtag snippet plus a GTM tag is the classic triple-fire: three purchases per order, and GA4 revenue at 250 percent of real.
5. Refunds, cancellations and failed orders
WooCommerce reports on completed and processing orders. GA4 keeps every purchase event forever unless you send a refund event, which almost nobody does. A 4 percent refund rate is a 4 percent permanent overstatement in GA4 and a 4 percent understatement of your gap.
6. Tax, shipping and currency
Does your purchase event send the order total, the subtotal, or the subtotal before discounts? Does it include shipping? Is it in the store currency or the display currency? Each of those is a different number, and most tracking plugins pick one without telling you. A store with 10 percent tax and free shipping over a threshold can show a steady 8 to 12 percent “gap” that is just definitions.
7. Time zones and reporting lag
WooCommerce reports in the site time zone; GA4 in the property time zone. If they differ, every daily comparison is off by the orders in the overlap hours. GA4 also takes 24 to 48 hours to finalise a day, so yesterday is always low.
Measure your own gap in thirty minutes
- Export a week of WooCommerce orders. WooCommerce → Orders, filter to completed and processing, export (or use
wp wc order list --status=completed,processing --after=…). You want order number, date, total and payment method. - Pull the same week from GA4 by transaction ID. Explore → Free form, dimension Transaction ID, metrics Purchases and Purchase revenue, date range the same week. Export to CSV.
- Join on the ID. A spreadsheet VLOOKUP is enough. Every WooCommerce order with no GA4 row is a missed purchase. Every GA4 row with a purchase count above 1 is a duplicate. Every GA4 ID that is not a WooCommerce order is a ghost (a test order, a failed order that fired anyway).
- Compute three numbers. Match rate = matched orders ÷ WooCommerce orders. Duplicate rate = (GA4 purchases − matched orders) ÷ matched orders. Value drift = average GA4 revenue per matched order ÷ average WooCommerce total for the same orders.
- Break the misses down by payment method. If PayPal and Klarna orders miss at three times the rate of card orders, reason 3 is your problem. If misses are spread evenly, it is consent and blockers.
Those three numbers tell you which of the seven reasons to fix first. A 70 percent match rate with a 0 percent duplicate rate and value drift at 1.00 is a consent and delivery problem. A 95 percent match rate with 30 percent duplicates is a dedupe problem. Value drift at 0.91 with 10 percent tax is a definition problem.
Fix it in this order
- Remove duplicates first. Pick one source of truth for the purchase event: one plugin, or one GTM tag, never both. Add a dedupe on
transaction_idso a refreshed thank-you page cannot fire again. This fixes overstatement in a day and is the change ad platforms feel most. - Pin down the value definition. Decide, write it down and send it: order total including tax and shipping, in the store currency, is the least surprising. Set GA4’s currency to match. Re-run the value drift check.
- Make the browser event fire before the redirect, not after. For offsite payment methods, fire the purchase on the “returning you to the store” step or, better, do not rely on the browser for those orders at all (next step).
- Send the purchase from the server too. GA4’s Measurement Protocol lets WooCommerce post the purchase when the order is marked paid, carrying the same
transaction_idand the client ID captured at checkout. The browser event and the server event dedupe on the ID, so consent-declined, blocked and abandoned-redirect orders all get counted. Do the same for Meta via the Conversions API. This step alone usually moves the match rate from the 70s to the mid-90s. - Send refunds. When an order is refunded, post a
refundevent with the same ID. GA4 revenue will finally go down when yours does. - Align the time zones in WooCommerce and GA4, and compare finished days only.
- Put a tripwire on it. Every morning, compare yesterday’s GA4 purchases to yesterday’s WooCommerce orders. If the ratio drops more than ten points below its 14-day average, something changed: a theme update, a consent banner edit, a new checkout plugin. This is the step nobody does, and it is the reason the gap comes back three months after you fixed it.
How to tell it worked
Re-run the thirty-minute check a week after the changes. You are looking for a match rate above 95 percent, a duplicate rate under 2 percent, and value drift between 0.98 and 1.02. Then stop looking at totals. Totals will still differ, because reasons 1, 2 and 7 never fully go away, and that is fine: the ad platforms are being fed every order with the right value, and you know within a day if that stops.
If the match rate is still under 90 percent after server-side events, the usual culprit is the client ID: the server event is arriving without the _ga value from the browser, so GA4 cannot stitch it to a session and drops it from most reports. Capture the cookie at checkout and store it on the order.
Keep it from breaking again
Every gap we have ever been asked to fix was once fixed before. Tracking breaks silently and nobody notices until the quarterly review. The cheapest insurance is a daily baseline per event with an alert when it moves, which is exactly what TagEasy does: it watches the purchase, add-to-cart and lead events on your site, reconciles revenue against your orders, and sends the alert to Slack or email the day a tag stops firing. The free tier covers one site with no card required. For a per-order verdict inside WooCommerce itself, with a reason code for every miss, Revenue Recon is on the roadmap.
The tool for this
Find the gap on your store
TagEasy is a conversion tracking monitoring tool for agencies and online stores that run paid ads and need to know when tracking breaks. Free for one site · no credit card