GA4 Ecommerce Revenue Does Not Match Your Orders: How to Find the Gap
You export last month's revenue from GA4, put it next to the order report from your shop, and the two numbers differ. Usually GA4 is lower. Sometimes it is higher. The case worth a week of your time is the gap that repeats at roughly the same percentage every month, because a stable gap is a broken feed rather than a rounding error.
The two systems answer different questions. Your shop reports money that arrived. GA4 reports purchase events it managed to collect and tie to a session, carrying the values your tags sent. Both can be correct and still disagree, so the job is not to make them equal. The job is to know why they differ, and by how much.
What GA4 counts as revenue
The metrics do not all mean the same thing, and most reconciliation arguments come from comparing the wrong pair.
- Total revenue. Google's definition is ecommerce purchases, in-app purchases, subscriptions and advertising revenue, including AdMob data, minus refunds given.
- Purchase revenue. Ecommerce and in-app purchases only.
- Item revenue. Price multiplied by quantity, taken from the items array. It excludes tax and shipping.
Event-level revenue comes from the value and currency parameters on the purchase event. Item-level revenue comes from price and currency. Google is explicit about value: set it to the sum of price times quantity for all items in the purchase, and do not include shipping or tax. If your tag sends the gross order total that your checkout page shows the customer, you have just built the most common gap in ecommerce reporting on purpose.
One failure mode is worse than a wrong number. If a required parameter is missing, the event is treated as a custom event and does not appear in the report at all. A purchase that arrives without items, or without currency alongside value, is not undercounted. It is invisible.
Check the clock before you check the tag
Three properties of GA4 processing explain more gaps than anyone expects. Skip this section and you will spend an afternoon debugging a tag that works.
- Processing takes 24 to 48 hours, and data in reports can change during that window. A comparison run on data from yesterday morning is a comparison against a partial file.
- Daily data availability depends on property size. Google's table gives 12 hours for standard properties, 18 hours for Premium Normal, and 24 or more for Premium XLarge. Intraday data arrives sooner, in 2 to 6 hours, and its traffic source dimensions can carry temporary gaps until the daily set lands.
- Attribution credit for key events can keep changing for up to 12 days after the event, as Google's modelling improves. A channel report for last week is a draft, not a final score.
Practical rule: reconcile a closed month, and leave the last 48 hours out of both sides of the comparison. If you are reviewing intraday data, expect the paid and organic last click model to be used by default, and expect more rows collapsing into (other) while cardinality limits are tighter.
Cause one: transaction IDs that are missing or shared
A transaction ID is not optional decoration. Google requires one on every ecommerce event, both to deduplicate transactions from the same user and to process refunds. The details matter more than the summary.
- Deduplication only works for data collected through web streams, not app streams. The same logic does not cover your app.
- Never send an empty string. GA4 deduplicates every purchase event that carries transaction_id set to an empty string, so a container that falls back to a blank value can collapse an entire month into one purchase.
- Never reuse one ID across different orders. Google warns that a static ID can significantly undercount your key events. Reusing an ID across different users is wrong for the same reason.
- IDs can contain numbers, letters, dashes and spaces, and must be unique per order. They must not contain anything that could identify a customer.
The useful test is a diff rather than a look. Pull the transaction IDs for purchase events out of GA4 for the month, put them next to your order references, and compare the lists in both directions. Orders present in your shop but absent from GA4 point at collection, consent or a conditional tag. Purchase events in GA4 with no matching order point at duplicates or at a test transaction someone forgot to remove.
Cause two: two tags sending the same purchase
On Shopify, setting up Google Analytics collects some ecommerce events for you. Google names add_to_cart, begin_checkout and purchase as the events Shopify handles on your behalf. If you also built a purchase tag in Google Tag Manager, both fire. Deduplication only saves you when the IDs match, and in a double-tagged container they usually do not, so the second event counts as a second sale.
The same failure appears on custom builds. A purchase tag that fires whenever the confirmation page renders will fire again on a refresh, a back navigation or a bookmarked return, unless the ID is stable and the platform suppresses repeats. Check the event count against your order count before you check the revenue, because duplicate events inflate both.
To audit it: open Tag Assistant on a test order, confirm which containers and tags fire on the confirmation page, and check the network calls for one purchase event carrying one transaction ID.
Cause three: tax, shipping and discounts
If the values do not match while the counts do, this is where to look first. GA4 expects value to exclude tax and shipping, and item revenue to be price times quantity. Finance counts gross order value including both. A UK shop on standard rate VAT with free delivery over a threshold will see a gap that moves with order size, which is exactly what a tax exclusion looks like.
Two checks settle it. Divide GA4 purchase revenue by your order count and compare it with average order value ex-VAT and ex-delivery. Then look at whether your value parameter is wired to the same field your checkout shows the customer. Discounts belong in item-level discount and coupon parameters, applied consistently, not subtracted twice in one system and once in the other.
Cause four: currency
Currency is required for revenue metrics whenever you set value, and Google expects a three letter ISO 4217 code. A currency symbol, a lowercase code or a blank breaks the calculation quietly.
Selling in more than one currency needs a decision before the hit, not after. Choose the currency you report in, convert at the point where the value is calculated, and send one consistent code and value pair. Do not rely on GA4 to convert between currencies for you, and do not mix codes inside one property unless you want a revenue total that matches no bank statement you hold.
Cause five: consent, blocked requests and thresholds
With analytics_storage denied, GA4 has no cookie identifier to work with, so a purchase from that visitor is not attributed to a session the way a consented purchase is. If a third of your visitors decline analytics storage, expect a share of your orders to be missing from GA4 before you touch a tag. Measure your decline rate and put it next to the gap. Our Consent Mode v2 guide for UK sites covers the modelling side, including what has to be true before Google will estimate that missing activity.
Ad blockers and browser tracking protection drop the measurement request entirely, which no consent setting recovers. Separately, report-level thresholding hides rows when user counts are low, and sampling changes the numbers when a query exceeds the event quota. The Data quality indicator next to a report title tells you which of those is in play. If it says thresholding applied, you are looking at a partial table, not a broken feed.
Cause six: refunds that never get sent
Refunds deduct from revenue metrics, and Google's definition of total revenue says so directly. That makes the refund feed as important as the purchase feed, and it is the one most teams forget.
- A refund event needs the transaction_id of the original purchase, plus items defined with item_id and quantity if you want item-level refund metrics.
- Without the matching ID, a refund cannot be applied to the order it belongs to.
- Refunds processed in the back office, by phone or by bank transfer, never appear unless you send them from the server or accept them as a known gap.
A store with a 12% return rate that sends no refunds overstates GA4 revenue by roughly that order of magnitude within a quarter, and the effect is seasonal. January's number looks like a tracking failure when it is a returns pattern that was never reported.
Two smaller causes worth naming
Hosted checkout pages hosted by a payment provider do not carry your tag. The purchase event fires when the buyer returns to your confirmation page, and if that return lands on a fresh cookie, the purchase arrives as a new session credited to direct traffic. Revenue survives, the source does not.
Second, any checkout on a domain or subdomain missing from the cross-domain list splits the session at the point of payment. The purchase still counts, but the acquisition data behind it belongs to a different session. Both problems move revenue between channels without changing the total, which is why they get missed in a revenue-only audit.
A monthly reconciliation routine
Thirty minutes a month, run in this order, will answer the question rather than adding to it.
- Pick a closed month. Exclude the last 48 hours from both sides.
- Export order count and revenue from the shop, adjusted for tax and shipping so the figure is comparable.
- Export purchase events, transaction IDs and purchase revenue from GA4, through an exploration or the Data API.
- Compare counts first, then IDs, then values. Counts matching and values not is a value problem. Counts missing is a collection or consent problem. Counts higher than the shop is a duplicate problem.
- Write down the gap as a percentage and the reason you can defend. Repeat next month and watch whether the reason holds.
- If you need per-order truth rather than a report, reconcile in BigQuery against the raw export, where report thresholds and sampling do not apply.
What to tell finance
GA4 is a marketing instrument. Use it to compare channels, campaigns, products and pages against each other, and to feed platforms that optimise spend. Do not use it as the revenue ledger, and do not let a monthly management pack quote GA4 revenue without a note on what it excludes.
Agree an acceptance range with whoever owns the numbers, in writing. A few per cent is normal once tax, shipping, unconsented users and unsent refunds are accounted for. Anything wider needs a named cause. Then check the assumptions quarterly, because a consent rate change or a new payment provider will move the gap without anyone touching a tag.
One last point that cuts the other way. Google Ads optimises on what your feed sends. If GA4 under-reports conversions because of a broken parameter or a missing refund feed, your bidding follows the wrong signal, and the fix pays for itself in spend rather than in reporting comfort.
Does Your GA4 Revenue Survive an Audit?
North Digital reconciles GA4 ecommerce data against order systems for UK shops, agencies and their finance teams. You get the transaction ID audit, the value and currency rules, the refund path, and a written explanation of whatever gap is left.
Book a Revenue Reconciliation