Missing purchase events in GA4? Here are 12 reasons why
Your WooCommerce order is in the admin but the purchase event never reached GA4. Twelve reasons why, from consent mode to gateway redirects, and the fix for each.
A missing purchase event is still the question I get most often through the support forum and by email. The order is sitting in the WooCommerce admin, the customer has paid, and Google Analytics 4 reports fewer purchases than the store did. This article was first published in 2017, when Universal Analytics was still the only version of Google Analytics there was. One of the ten reasons it listed no longer exists, another changed its shape, and several new ones have appeared since, so I rewrote it for GA4 in August 2026.
The short version: a purchase event only reaches GA4 if the buyer’s browser loads a page that pushes it, if consent allows the request to leave that browser, and if the event carries a transaction ID that GA4 has not already seen. Every reason below is one of those three links breaking.
First question: are you missing some purchases, or all of them?
Answer this before anything else, because it decides whether the rest of the article applies to you at all.
Every reason below explains why a store that tracks most of its orders loses some of them. None of them explains a store that tracks none of its orders. If your GA4 purchase count is zero, or so far below your order count that “some” is the wrong word, the setup itself is wrong. Twelve explanations for a partial loss are of no use while nothing is being sent at all, and working through them is a long detour that ends where it started.
Five things to confirm before you go looking for a leak:
- The Google Tag Manager container code is on the site, and it is on the order received page too. A cache plugin, an optimization plugin or a consent tool can remove it from that one page while leaving it everywhere else.
- The Track e-commerce option is enabled in the GTM4WP WooCommerce settings. It is the master switch, and nothing else in that section does anything while it is off.
- Your container has a GA4 configuration with the measurement ID of the property you are looking at, and not the one from a different property or an old one.
- There is a GA4 event tag triggered by the
purchasedata layer event, and it is set to send ecommerce data. - That container version is published, not just previewed.
If any of those is missing, the GA4 e-commerce setup guide is the page you want, not this one. Come back here once a test order shows up correctly and real orders still do not.
Find where the purchase event is lost
With the setup confirmed and purchases still going missing, place a test order and watch it in two places at once: Google Tag Manager Preview mode, and GA4 DebugView. It takes a few minutes and it tells you which part of this article you need.
| What you see | Where the problem is | Reasons to read |
|---|---|---|
No purchase event in the data layer at all | The page that should push the event was never loaded, or the plugin decided not to track that order | 1, 2, 3, 4 and 8 |
| The event is in the data layer, but no request goes to Google | Something between the data layer and Google is stopping it | 5, 6, 7 and 9 |
| The request is sent, but the purchase is not in your reports | GA4 received it and did something else with it | 10, 11 and 12 |
Where a GTM4WP setting is the answer, the settings named below are the ones in version 2.0. The three that address a customer who never sees the standard order received page are all new in that version.
Part 1: the buyer never reaches the page that fires the event
#1 The payment gateway does not send the customer back
This is still the most common cause, and the one that surprises people most.
Google Tag Manager runs in the browser. There is no way to fire a tag in your container from your server. Your store learns about the payment through a webhook or an IPN callback, entirely in the background, and marks the order as paid. If the customer closes the tab at the payment provider, or goes elsewhere on the provider’s site instead of clicking the return link, no browser ever loads your order received page, so the order exists in your store while the event was never fired anywhere.
What to do:
- Check the gateway settings. Most providers have an auto return or an instant redirect option that sends the buyer back to the store automatically. Turn it on where it exists.
- Turn on Reliable purchase tracking in GTM4WP. The plugin remembers the placed order on the server and fires the purchase event on the next page the same browser opens, so a buyer who lands anywhere on the site is still counted. It is off by default and marked experimental. It cannot help with a buyer who never returns to the site at all.
- For gateways where the buyer genuinely never comes back, the only complete answer is server-side tracking, which is reason 4 below.
#2 The store uses a custom thank you page
WooCommerce fires a specific set of hooks on its own order received endpoint, and GTM4WP listens to them. Some checkout and funnel plugins replace that endpoint with a page of their own and do not reproduce those hooks, so nothing on the page knows an order was just placed.
GTM4WP has an option for this: Custom order received (thank-you) page. Point it at your confirmation page and the plugin resolves the order from the buyer’s own session and fires the purchase event there. If your setup is more unusual than that, the plugin author of the checkout plugin is the right person to ask, because the fix is for their page to behave like the standard one.
#3 The order status is not one the plugin is set to track
The purchase event does not fire for every order that reaches the confirmation page. GTM4WP decides by order status, and the list is a setting: Order statuses that trigger the purchase event. The default covers Processing, On hold and Completed, so a cash on delivery order (Processing) and a bank transfer order (On hold) are both counted at checkout even though the money arrives later. Developers can change the same list with the gtm4wp_purchase_trackable_statuses filter.
Two situations produce a missing purchase here. The first is a narrowed list: someone restricted it to Completed, and now nothing is counted until the order is shipped, if it ever is. The second affects gateways that leave the order in Pending payment while the buyer is already looking at the confirmation page. Failed and pending orders are not tracked by default in GTM4WP 2.0, whereas 1.x tracked any order except a failed one that reached the thank you page, so this is worth checking after an upgrade.
Decide deliberately what you want here. Counting an On hold order means counting revenue that may never arrive. Not counting it means your GA4 conversion data is blind to a payment method your customers actually use.
#4 The order never went through a browser at all
Some orders have no visitor behind them:
- Orders you create by hand in the WooCommerce admin, or take over the phone.
- Point of sale orders.
- Subscription renewals, which are charged by a scheduled task on your server.
- Orders placed in a mobile app that has no measurement of its own.
None of these can produce a browser event, and no plugin setting will change that. They are a real part of the gap between your store reports and GA4, and on subscription stores they can be the largest part of it.
If you need them in GA4, they have to be sent from your server with the GA4 Measurement Protocol. Be aware of what that gets you: without a matching client_id and session_id the event is not joined to the visitor’s web session, so the purchase appears with no traffic source attached to it. Google also documents hard limits on those requests, currently 25 events per request and a post body under 130 kB. Use it to complete your revenue total, and do not expect it to complete your attribution as well.
Part 2: the event fires, but nothing leaves the browser
#5 Consent mode is denying the request
This did not exist when the original article was written, and today it is the reason I would check first on any European store.
If your consent banner is wired to Google consent mode, Google tags read the consent state before they send anything. Which purchases you lose depends on which variant you run:
- Under advanced consent mode, Google tags load immediately and send cookieless pings even while consent is denied. The event still reaches Google, and once a consent update follows, GA4 applies session and identity context to what was already collected. The order in which your events and your banner run largely stops mattering.
- Under basic consent mode, nothing is sent before consent is granted, not even a ping. If your purchase event fires before your consent tool sends its update command, that purchase is gone and no tag configuration recovers it.
The practical checks:
- Send your consent mode default command from the Consent Initialization trigger in Google Tag Manager. It runs before every other trigger, so a data layer push that happens early is covered rather than uncovered, and a returning visitor’s stored consent is applied early enough to matter.
- In GTM Preview, look at the consent state at the moment the purchase tag would fire, not at the end of the page.
- Check the additional consent checks on the tag itself. A tag set to require consent that never arrives simply never fires.
GTM4WP itself decides nothing about consent. It sends the default command if you ask it to, and it bridges a few specific consent tools into the data layer. The consent mode and consent tools pages cover which of those you need, and the GDPR page covers the wider question of what needs consent in the first place.
Consent mode also changes what the reports should be expected to show. GA4 may model part of the behavior of visitors who declined, so some of the shortfall in front of you was never collected as a row and never will be, even while the totals move to account for it.
#6 An ad blocker or a privacy extension blocks the request
Popular content blockers block Google Analytics requests along with advertising. For those visitors you usually know nothing at all, not even that they were on the site. Google’s own Analytics opt-out browser add-on is still published and still works against gtag.js, and some privacy policies link to it directly.
Server-side tagging changes the shape of this problem. With a server-side container the browser talks to a first-party endpoint on your own domain rather than to Google, which many blocklists do not recognize. That solves a genuine measurement problem, and it is also where the ethical question starts.
My position has not changed since 2017. A blocked request from someone who chose to block it is not a bug to be worked around, and a denied consent signal is a decision your visitor made. Consent mode applies inside a server-side container as well, so honoring it there is not optional either. Repairing a redirect that loses orders is measurement work. Collecting from people who asked not to be measured is not, and moving the container to your own domain does not change that.
#7 A JavaScript error on the confirmation page
Order confirmation pages collect scripts: upsell offers, review prompts, chat widgets, affiliate pixels, survey tools. If one of them throws an error before the container has processed your event, everything after it in that execution stops, and your purchase tag is often what is after it.
This is hard to see because it usually depends on a combination you do not have on your own machine. Open the browser console on a real confirmation page rather than a staging copy, and check whether the container is even loading. If the shortfall is intermittent and you cannot reproduce it, JavaScript error tracking is worth the setup: send caught errors to GA4 as events and you get the browser, the device and the operating system alongside the error message, which is usually enough to identify the combination that fails.
#8 A quote in a product name breaks the data layer
The data layer is written into the page as JavaScript. A product named Cool Product with 4" display contains the character that ends a string in JavaScript, and code that pastes the name into the page without encoding it produces a script the browser refuses to parse. The whole block fails, not just that one value, and the container that lives inside the block goes down with it. The same applies to an ampersand, an apostrophe and an angle bracket.
GTM4WP encodes every value it writes into a script with wp_json_encode() and the hex flags that escape quotes, ampersands and angle brackets, so product names with these characters are safe. Version 2.0 also handles the case where a value cannot be converted to JSON at all: the single value is left out and the rest of the block is written normally, instead of the whole block failing. An order whose event could not be written is no longer flagged as tracked either, so the next page view tries again.
Where this still bites is custom code. If you push your own values into the data layer from a theme template or a code snippet plugin, that is the place to check, and encoding is the fix rather than stripping characters out of your product names.
#9 The tag or the trigger in the container is wrong
A purchase event sitting in the data layer is not the same thing as a tag firing. The usual causes:
- There is no GA4 event tag triggered by the
purchasedata layer event. - The tag fires but sends no ecommerce data, because the “Send Ecommerce data” option is off or points at the wrong source.
- The trigger is a Page View on a URL pattern that no longer matches, for example after the order received endpoint changed.
- The tag is limited to once per page and an earlier blocked attempt consumed that one chance.
- The container change was never published. Preview mode uses your workspace; your visitors use the published version.
GTM Preview tells you which of these it is in about a minute, and GA4 DebugView tells you what GA4 actually received.
Part 3: GA4 received the event and you still cannot see it
#10 The transaction ID is empty or repeated
Google Analytics deduplicates purchase events that carry the same transaction ID. That behavior is there to protect you, and it turns into missing purchases when the ID is not what GA4 expects.
Two failures to look for. First, an empty transaction ID: every purchase sent with transaction_id="" is treated as the same transaction, so a whole day of orders can collapse into one. Second, an ID that is not unique across orders, which happens when a prefix or a template overwrites the order number rather than extending it.
On the WooCommerce side, the reverse problem produces duplicates rather than losses. GTM4WP guards against a buyer reloading the confirmation page by flagging the order on the server and writing a browser-side guard keyed on the order number, so the same purchase is not sent twice. The Do not flag orders as being tracked option switches those guards off, which is useful while testing and a mistake to leave on in production. If you use the Transaction ID prefix option, keep the prefix fixed, because changing it makes every already-sent order look like a new one.
#11 The order is too large for one event
GA4 has limits, and it does not report an error when it applies them. It drops what exceeds a threshold silently, which is why a large order can vanish while everything in your container looks correct.
The limit that loses whole purchases is the size of the data itself. Google documents a 16 kB ceiling on event data each time data is sent, and it applies to a single event and to a group of events alike. The important part is what happens to a group: the batch is still sent to the server, but the entire batch is not processed. One oversized event therefore does not fail on its own, it takes down everything that was sent with it.
A purchase event reaches 16 kB more easily than it sounds. Long product names, several category levels, brands, variants and item-scoped custom parameters all ride along on every line of the order. If you suspect this, open the network tab on a real confirmation page, find the request to Google and look at its size before you change anything else.
The fix is to send less per item, and GA4 gives you somewhere to put what you stop sending. Item data import takes your product catalog as a file and joins it to the events on the item ID, so the item name, brand, up to five category levels, the variant and your item-scoped custom dimensions can all live in GA4 rather than in every purchase event you send. The join happens at query time, which means it applies to data you have already collected and not only to what arrives next. Google allows up to five item data sources totaling 1 GB, and an upload takes up to 24 hours to show in reports.
That leaves the identifier and the numbers in the event, which is exactly what has to be there. On a WooCommerce store the place to trim is the gtm4wp_eec_order_item filter for the purchase items and gtm4wp_eec_product_array for the rest, where you can drop the fields you have imported instead. This is the GA4 form of the old Universal Analytics product data upload, and it is the only one of these options that makes the payload smaller without taking anything out of your reports.
Two other limits are worth knowing. The items array can include up to 200 elements, so a wholesale order beyond that loses the surplus and the transaction is counted while the product-level reports do not reconcile. Per event you also get 25 parameters, 100 characters per parameter value and 27 item-scoped custom parameters.
One limit from the 2017 version of this article is genuinely gone, and one only changed shape. The cap of 500 hits per session was a Universal Analytics limit and has no equivalent in GA4, whose collection limits are counted per user per day (100,000 events) rather than per session. The old 8 kB payload ceiling, on the other hand, did not disappear. It became the 16 kB limit above, so checking the payload size on a big order is still the right instinct.
#12 The event arrived and the report is not showing it
Before concluding that data is missing, rule out the report itself:
- Google documents a data freshness interval of 2 to 6 hours for standard intraday data and 12 hours for daily data on standard properties. Realtime and DebugView are where you look immediately after a test order. The standard reports are not.
- The property time zone may not be yours, and a late evening order then lands on the next day.
- An internal traffic or developer filter set to Active excludes matching traffic permanently, your own test orders included.
- Data thresholding withholds rows from a report when showing them could identify individual users, which happens most often once demographics or Google signals are involved. The data exists and the row is hidden, so a wider date range sometimes brings it back.
- If the purchase count matches and only the revenue does not, nothing is missing. Check the Exclude tax from revenue and Exclude shipping from revenue options against how your store reports its own totals.
How large a gap is normal
There is no honest single number, because the answer depends on how much of your traffic declines consent, which payment methods you offer and how many orders you create by hand. The shape of the gap is more useful to you than its size.
A gap that is stable from month to month is made of the reasons you cannot remove: consent refusals, blockers, offline orders. Measure it once, write it down and use it as your correction factor. A gap that appears suddenly, or that only affects one payment method, is a bug, and it is almost always in Part 1 or Part 2 above.
Common questions
GA4 shows no purchases at all. Which reason is it?
None of them. Every reason in this article explains a store that loses some of its orders, and none of them takes a working setup down to zero. Check the five setup points at the top of the article, then use the GA4 e-commerce setup guide. Twelve causes of a partial loss have nothing to say about a store that never sent a purchase event.
Why does GA4 show fewer purchases than WooCommerce?
Because the two count different things. WooCommerce counts every order that reaches its database, including admin, phone, subscription renewal and point of sale orders. GA4 counts purchase events that a browser successfully sent and was allowed to send. The difference is never zero.
The purchase event is in my data layer but not in GA4. What now?
Does GTM4WP recover a purchase when the customer never sees the thank you page?
Partly. Reliable purchase tracking fires the event on the next page that browser opens, so a buyer who comes back to the site anywhere is counted. A buyer who pays and never returns cannot be recovered from the browser at all, and that is the case where server-side tracking is the only option.
Can I make GA4 count orders paid by bank transfer?
Yes. Add the relevant order status to the Order statuses that trigger the purchase event setting. On hold is included by default.
Can one large order break the tracking of other events?
Yes, and it is the part people miss. Google’s 16 kB limit on event data applies to a group of events as well as to a single one, and when a group is over the limit the whole batch goes unprocessed rather than just the offending event. A single huge order can therefore cost you the events that happened to be sent alongside it.
Will server-side tagging fix all of this?
No. It helps against blockers. It does nothing for a customer who never returns from the gateway, for an order that never had a browser behind it, or for a visitor who denied consent, because consent mode applies in a server-side container too.
Summary
Ten reasons became twelve, and the biggest new one is consent. Work through it in the sequence the article uses: confirm the setup works at all, then establish whether the event exists, then whether it left the browser, then what GA4 did with it. Skipping the first step is the mistake that costs the most time, because none of the twelve reasons applies to a store that never sent a purchase event in the first place. Once the setup is confirmed, most of the time the answer is in Part 1, and most of the time it is either a gateway that does not redirect or a thank you page that is not the standard one, both of which GTM4WP now has a setting for.
Accept that the remainder will not go to zero. Fix the cases that are genuinely broken, leave the visitors who chose not to be measured out of it, and measure your own gap once so that you know how to read your reports afterwards.

After the payment is completed, the service will redirect me to the site back, but the transactions are not included in the analysis.
What should I do if the “Thanks” page was created by us?
If you are using a custom thankyou page, you will need to ensure that some background code tells my plugin and other plugins that this is your order received page. You can do this my hooking into the woocommerce_is_order_received_page filter and return true on your custom page:
https://docs.woocommerce.com/wc-apidocs/source-function-is_order_received_page.html#191-199
Tell me, how is it easier to implement?
Are you managing this custom thankyou page by yourself with your own codes or you are using a plugin for that?
Do it yourself. Maybe you will recommend a suitable plug-in?
In that case you should create a new function in your PHP files and use the add_filter() WordPress function to hook into the woocommerce_is_order_received_page filter. You need to check whether the user is on your own thankyou page. This could be done using the is_page() WordPress function using the post ID of your thankyou page.
You will also need to ensure that the order key is present in the URL of the custom thankyou page like on the default page.
Rules of thumb: you need to exactly mimic the background behavior of the default order received page.
Thank you for your reply. However, I do not have enough skills to do this without the developer’s participation. Maybe there is a solution without the participation of a programmer?
Unfortunately this can not be solved without a programmer.
I plan to review a custom thankyou page plugin this weekend, it might work with my plugin as well. I will let you know if the test worked
Hey, did you end up finding a plugin? I have a custom thank you page as well and I am having the same problem! I have added the woocommerce_is_order_received_page filter but the URL of my thank you page does not append any order ID.
Yes, this seems to work perfectly with my plugin as well: https://wordpress.org/plugins/woo-thank-you-page-nextmove-lite/
Nice One!
It’s funny thought, that duplicate transactions are just as usual as the above mentioned data discrepancies, so a lot of times the ecommerce tracking is screwed in all directions without a carefull setup.
Cheers,
Transactions are de-duplicated on the transaction level, thus if you send in the same transaction ID with the exactly same transaction content (revenue, item details, etc) – it should not count twice in your reports