Skip to content

Analytics

Reconciling GA4 with Shopify orders, line by line

GA4 and Shopify never agree to the order. A line-by-line reconciliation shows which orders GA4 missed and why, so you can trust the gap instead of guessing.

By CartKernel · Published

Shopify counts orders. GA4 counts purchase events that reached it. Those are different populations, and a gap between them is normal on every store. What is not acceptable is not knowing what the gap is made of. A line-by-line reconciliation matches every Shopify order to a GA4 transaction and classifies each missing one by cause, which turns “GA4 is wrong” into a list of things you can size, fix or accept. This is how we run it.

Pull both lists for one closed month

Work on a full calendar month that ended at least three days ago, so GA4 has finished processing.

From Shopify. Export orders for the month from the Orders page. Keep these columns: order name, order ID, created date, sales channel, financial status, payment gateway, currency, subtotal, shipping, taxes, total, discount code, and tags. Timestamps in the export are in the store’s time zone.

From GA4. In Explore, build a free-form table with the Transaction ID dimension and the Purchases and Purchase revenue metrics for the same dates, and export it. Two settings decide whether this works. Data retention for event-level data is two months by default and can be set to fourteen months; if it is still on two months, Explore cannot see a month that old, so change the setting now for next time. Explore also samples above ten million events per query, which a month of purchases rarely approaches. If the property has a BigQuery export, query the purchase events there instead; it is unsampled and keeps the history.

Which identifier to join on. Shopify has both an order name, such as #1042, and a numeric order ID. Check which one your GA4 integration sends as transaction_id by looking at one row; the export includes both, so join on whichever matches.

Join and sort every order into a bucket

Left-join Shopify to GA4 on the transaction ID. Every order lands in one of four buckets.

  1. Matched, same value. The order reached GA4 with the revenue you expect.
  2. Matched, different value. The order is there, but the revenue differs.
  3. Missing from GA4. The order exists in Shopify with no GA4 transaction.
  4. Present in GA4 only. GA4 has a transaction Shopify has no record of.

Bucket three is the one people argue about. Bucket four is the one that tells you the tracking is double counting.

Why an order is missing, and how to tell

Cause How to recognise it in the join What to do
Order placed outside the web store Sales channel is POS, draft order or a marketplace Exclude from the web population before computing the gap
Subscription renewal Tags or app name on the order, no session Exclude, or send server-side if you want it in GA4
Off-site payment return Gateway is a wallet or instalment provider that leaves the store Send the purchase from a server on the paid webhook, deduplicated on transaction ID
Consent declined or tracking blocked No pattern by channel; skews to certain regions and browsers Consent Mode set up correctly, then server-side collection through a first-party endpoint
Thank-you page never loaded Isolated cases, often mobile Same server-side fix
Integration stopped A whole day or range with nothing matched Alerting on daily purchase counts

The last row is the one to check first. If a full day is empty, the rest of the analysis is noise until the pixel is repaired; GA4 purchase event not firing covers the diagnosis on Shopify. Shopify’s checkout_completed pixel event is what a custom pixel forwards as the GA4 purchase, and its payload is documented in the Web Pixels API reference.

Why GA4 has orders Shopify does not

  • Duplicates. The thank-you page reloaded, or two integrations both send a purchase: the Google and YouTube channel app plus a custom pixel, or a theme snippet left behind from an older setup. One transaction ID appearing twice in GA4 is the signature. Choose one path and remove the others.
  • Test orders placed with a test gateway, and orders later deleted.
  • Cancelled orders. GA4 keeps the purchase; Shopify shows it cancelled. This is a value question rather than a tracking fault, and it belongs in bucket two.

Why the value differs on matched orders

Most value differences come from a definition nobody wrote down.

  • GA4 value on the purchase may include or exclude tax and shipping depending on how the pixel was built. Shopify’s export separates subtotal, shipping, taxes and total. Compare against each until you find which one GA4 was sent, then document it.
  • Stores using Shopify Markets sell in several presentment currencies. GA4 converts to the property currency at its own rate; Shopify reports in the shop currency at the rate applied at checkout. Small differences on every international order are expected.
  • Refunds and order edits after the purchase change the Shopify total and do not change the GA4 purchase unless a refund event is sent.
  • Discount codes applied after the pixel captured the cart, or gift card payments, shift the total on one side only.

An illustrative reconciliation

Say a store exports 1,000 orders for the month. Sixty are POS and draft orders, so the web population is 940. GA4 shows 812 transactions. The join produces 790 matches, 22 transactions in GA4 with no Shopify order, and 150 web orders missing from GA4. Of the 22, 18 are the same transaction IDs twice, from a second pixel, and 4 are test orders. Of the 150 missing, 45 paid through a wallet that never returned the shopper to the store, 30 are subscription renewals, and 75 have no identifiable cause, which is the consent and blocking bucket. The numbers are made up to show the shape: two of the three missing causes are fixable with configuration, and the third is partly recoverable with architecture. Coverage on web orders is 790 divided by 940, and that ratio is the number to watch every month.

Turn the result into a monthly coverage ratio

Coverage is matched web orders divided by web orders in Shopify. Compute it monthly, chart it, and treat a move of more than a few points as an incident to investigate. Use it to read GA4 channel reports honestly: if coverage is four in five, channel revenue in GA4 is a sample, not a total, and the sample is biased toward regions and devices that allow tracking. Shopify stays the revenue of record. Why does GA4 show less revenue than Shopify gives the short version for anyone who asks, and GA4 revenue does not match Shopify is the troubleshooting page.

The fix order

  1. Remove duplicate purchase paths, because they hide the true gap.
  2. Add a daily alert on GA4 purchase count against Shopify order count.
  3. Send server-side purchases for off-site payment returns and subscription renewals, deduplicated on transaction ID.
  4. Set up Consent Mode properly and move collection to a first-party server endpoint; does an ecommerce store need server-side tagging explains when it earns its cost, and server-side tagging describes the setup.
  5. Write the value definition into the data layer spec so the next developer keeps it. The consent mode entry explains why the fourth bucket will never reach zero.

Steps one and two take an afternoon. Steps three and four are a small project inside the GA4 ecommerce tracking work. Run the reconciliation again the month after each change so the coverage ratio records what each fix recovered.


Sources

Find the leak.

A free Growth Analysis ranks what your store should fix first, by revenue at stake.