WooCommerce stores lose conversion data in two places: the browser, where blockers and tracking prevention drop tag requests, and the checkout itself, where a customer can leave the thank-you page before a client-side purchase tag ever fires. A server-side setup addresses both. The browser sends structured ecommerce events to a first-party endpoint, and the store's own backend sends the order a second time, from PHP, where no blocker can reach it. This guide covers how to build that with the Tracking Hippo plugin and a server container.
What server-side tracking changes for a WooCommerce store
Server-side tagging splits collection from delivery. The browser's only job becomes describing what happened, in a data layer, and sending it to one endpoint on your own domain. Decisions about which platforms receive the event, what is stripped from it and how it is formatted move into a container you control.
For a store, the practical gain is that purchase data stops depending on whether a third-party script survived the page. The order exists in the database whether or not the browser cooperated, so it can be delivered from the server as the authoritative record.
The ecommerce events a WooCommerce store should emit
GA4 defines a standard funnel, and the value of using the standard names is that every downstream platform already understands them. The plugin emits the complete set rather than a subset, because a funnel with gaps cannot be used for drop-off analysis and the missing steps are usually the ones that explain a decline.
Item detail matters as much as the event names. Remarketing audiences and Merchant Center matching depend on the item id agreeing with your product feed, so the id source is configurable rather than assumed.
Why the Cart and Checkout blocks need different handling
Most WooCommerce tracking integrations were written against the classic shortcode templates and hook into template actions such as woocommerce_after_cart. Those hooks do not exist in the block-based cart and checkout, which have been the default for new stores since WooCommerce 8.3. An integration that relies on them silently reports nothing on a modern store, and the gap is easy to miss because the product pages still work.
Two changes fix it. Page-level events are derived from the cart itself using WooCommerce's conditional tags, so they fire regardless of how the page is rendered. Cart mutations are read from the wc/store/cart data store, because the DOM events the blocks dispatch carry no item payload of their own.
Consent, identity and the order webhook
Consent Mode defaults have to be published before the container loads, exactly once. If your consent banner already sends them, a second set from a plugin creates a race that is hard to debug. Decide which component owns the defaults and switch the other one off.
Identity is the other half. Enhanced Conversions and Customer Match only match hashed values that were normalised the way Google expects first, and the most common failure is phone numbers. A number typed as +31 (0)6 1234 5678 must be hashed as +31612345678, because the parenthesised trunk zero is dropped in international format. Hashing the string as typed produces a value that never matches anything.
Verify the funnel on a real order, not in the settings screen
Configuration screens tell you what is switched on, not what arrived. Place a genuine test order on both the classic and the block checkout, watch each event in the container's preview mode, and confirm the purchase reaches its destination exactly once. Reload the thank-you page and confirm it does not arrive twice.
WooCommerce server-side tracking: implementation checklist
Work through this in order. Each step has an output you can verify before moving on, which is what keeps a tracking rebuild from turning into a guessing exercise.
- 01
Provision the container and a first-party subdomain
Deploy a server container and point a subdomain of the store's own domain at it. A container on an unrelated domain still works, but the cookies it sets are not first-party and the attribution advantage is lost.
- 02
Install the plugin and connect it
Upload the plugin ZIP, activate it, then enter the container URL and the GTM web container ID. Use the connection test to confirm the container answers before configuring anything else.
- 03
Decide who owns consent
If your consent banner publishes Consent Mode defaults, leave the plugin's consent tab switched off. If it does not, enable it and set the seven signals, the region scope and the wait-for-update window.
- 04
Match item ids to your product feed
Choose whether item_id is the SKU or the product ID and add a prefix if your feed exports one. Compare a handful of items against Merchant Center before you rely on remarketing audiences.
- 05
Enable the server-side order webhook
Turn on purchase and refund delivery and point it at the container endpoint. Delivery runs on a background job with retries, so checkout is never slowed down and a transient failure is not a lost conversion.
- 06
Deduplicate before you compare numbers
The browser purchase and the server purchase describe the same order. Match them on the transaction id in the container so the destination counts one conversion, then reconcile a day of orders against the WooCommerce reports.
Measure the orders you actually took
A WooCommerce measurement setup is working when the revenue in your analytics matches the revenue in your store, and you can explain any difference. Server-side delivery of the order is what closes most of that gap, because it stops depending on the browser finishing its work.
Build it so the pieces are separable: the data layer describes behaviour, the webhook states what was sold, and the container decides where each goes. When a platform changes its requirements, and they do, you change one mapping rather than re-instrumenting the store.
WooCommerce server-side tracking: common questions
Do I still need a browser data layer if the order is sent from the server?
Yes. The server webhook covers purchases and refunds. Everything earlier in the funnel, from product views to checkout steps, only exists in the browser, and GA4 needs those events to attribute the purchase to a session and a source.
Does this work with the Cart and Checkout blocks?
It does, provided the integration does not rely on classic template hooks. Page events should come from the cart via WooCommerce's conditional tags, and add or remove events from the wc/store/cart data store, which is where the block front-end keeps its state.
Will the purchase be counted twice if I send it from the browser and the server?
Only if you do not deduplicate. Send a stable identifier derived from the order with both copies, usually the transaction id, and match on it in the server container so the destination platform records one conversion.
Can I run this alongside another Google Tag Manager plugin?
One plugin should inject the container. Two containers on one page means every event is collected twice and reporting doubles. Keep the data layer from whichever plugin you prefer and disable container output in the other.