The server-side purchase webhook

Last updated ·

How the purchase event reaches your server container

GTM Kit Premium can send the purchase event to your server container directly from your shop, without the customer’s browser being involved. That matters because the browser is the unreliable half of the journey: a payment provider can fail to return the customer to your shop, a container can be blocked, a script error earlier on the page can stop everything below it. When the browser event never arrives, the sale is never recorded.

The server-side event is not a replacement for the browser event. It is a second path to the same destination, and you choose how the two relate.

Choosing when the server sends

The purchase webhook trigger has three options.

On order creation. The event is sent as soon as the order exists, before payment is confirmed. Use this when you want the conversion recorded at the earliest possible moment and you are comfortable counting orders that may later fail payment.

On payment complete. The event is sent when the order reaches a paid status. This is the common choice: it records real, paid conversions, and it is the default for most shops.

Only when the browser did not confirm. The shop waits, checks whether the browser reported that the purchase reached Google Tag Manager, and sends the server-side event only if it did not. On a healthy checkout your shop sends nothing from the server. This is a specialist option for measuring how many sales the server-side path rescues. It is not the recommended default, and the two options above stay correct for most shops.

What the third option is actually for

With either of the first two options, both paths send, and your container decides what to do with two copies. That is the fail-safe arrangement, and nothing is ever lost. What it cannot tell you is which path delivered a given conversion, and whether it would have been lost without the webhook. A container that keeps whichever copy arrives first cannot distinguish a rescued sale from one where the server simply won the race, so any recovery rate you calculate from it is not a real number.

With the third option, everything the server sends is a conversion the browser did not deliver. That count is readable under WooCommerce, Status, Logs.

The wait

The third option adds a “Wait before deciding (minutes)” field, five minutes by default and adjustable between one and sixty. It gives the browser time to report before the shop decides. The field is blank until you save a value, and blank means five.

Before you switch this on

Four things change the behaviour of the third option, and all four are quiet rather than loud.

Remove any custom snippet that already does this. If you are running a custom snippet or must-use plugin that implements browser-confirmed sending, remove it before selecting this option. The two do not compose: the snippet detaches the plugin’s own sending step and does not recognise the new setting, so neither side sends the purchase and nothing is recorded at all.

Do not combine it with disabling the frontend purchase event. If the browser never sends the purchase, nothing ever confirms, so every order waits out the timer and then sends. You get the delay without the benefit.

Check your caching plugin’s JavaScript handling. A plugin that delays JavaScript until the visitor interacts with the page will hold the confirmation script long enough that many orders never confirm. The option then behaves as an always-send, which is safe but not what you selected it for.

Turn on WooCommerce logging. The recovery count is written to the WooCommerce log. Without logging enabled, the number this option exists to produce is not recorded anywhere.

What the confirmation proves, and what it does not

The confirmation tells your shop that Google Tag Manager loaded, was processing the data layer, and had the purchase event available to it. It does not prove that a specific purchase tag inside your container fired. A tag that is misconfigured or paused is a container problem, and this option will read it as a successful browser delivery.

Attribution on a recovered conversion

A conversion the webhook recovers can keep the traffic source of the visit it belongs to, rather than arriving with no session attached and showing its source and medium as “(not set)”.

This used to fail in a specific and counterintuitive way. When a shop runs its tagging through its own subdomain, the analytics session and visitor cookies are commonly set so that JavaScript cannot read them at all, so the capture running in the visitor’s browser had nothing to record. The better your server-side setup, the more reliably attribution was lost. Those identifiers are now also read from the order request itself, where the cookies are visible whatever they are set to. Anything the browser did capture is kept as it was.

Your server container needs the updated template. The session details now travel in the payload, but your container has to be told to read them. Re-import the template from the Template Assistant once after updating. Until you do, the data is arriving and being ignored.

If your GA4 client uses Server Managed cookies

In your server container, the Google Analytics: GA4 client can manage its cookies in two ways, JavaScript Managed or Server Managed. Server Managed sets a first-party FPID cookie and gives you the two-year lifetime that comes with it. It has one side effect that matters here: unless Migrate from JavaScript Managed Client ID is ticked in the client’s settings, the client replaces the browser’s client ID with an ID derived from FPID before it forwards each hit to Google Analytics.

A recovered purchase carries the client ID from the browser’s _ga cookie, because that is the only client ID your shop can see. If the client has been forwarding the browser’s own page views under a different ID, the recovered purchase lands on a Google Analytics user that has no page views and no session. In reports that shows up as (not set) for Session source / medium and direct / (none) for First user source / medium, even though the visit itself was recorded.

What to do: in the server container, open the GA4 client, expand More Settings, and tick Migrate from JavaScript Managed Client ID. You keep Server Managed cookies and the two-year FPID; only the forwarded client ID changes, to match the browser’s. Nothing else needs to change, and no re-import is needed for this.

If your container came from the Template Assistant. Containers generated from 2 September 2026 onwards already have this ticked, and there is nothing for you to do. A container generated before that date has it unticked, whichever platform you generated it for: the GA4 client is part of every server container the assistant builds, not only the Google Analytics ones. Either tick the box as described above, or re-import the template, which picks up the session details mentioned above at the same time.

In our testing the change took effect immediately, including for returning visitors who already carried an FPID from before the setting was ticked. You do not need to wait for cookies to expire.

How to check, in two minutes: open sGTM Preview, pick any incoming browser page_view, open Outgoing HTTP Requests from Server, and compare the cid on the outgoing Google Analytics request with the visitor’s _ga cookie value. If they are identical, the substitution has stopped and your shop and your page views are reporting the same visitor. If they differ, the substitution is still happening.

Two things that look wrong in Preview but are not:

  • On a recovered purchase, the Event Data tab shows the session details under cookies, not as ga_session_id. The mapping is only visible on the outgoing request, where it appears as sid. Seeing them under cookies means the payload is right.
  • Replaying an older order through Preview shows you the request on the wire, and that is all it can show. Google Analytics attributes in processed reports, which can take up to 48 hours, so only a fresh order placed during a real visit can tell you whether attribution holds. A replay is not evidence either way.

With the box ticked, a recovered purchase is attributed to the visit that produced it. The server-side event carries the visitor’s client ID and session details, and Google Analytics files it in the same session as the browser’s own page views, with that session’s source, medium and campaign. Verified on real recovered orders in September 2026.

Two limits remain: a container with the box unticked records the purchase against a visitor Google Analytics has never seen, so its source shows as (not set); and a visitor whose browser blocked Google Analytics entirely has no session to join, so that purchase is counted with no source, which is still better than not counting it.

Related articles

← Previous

Previous article

Next →

Next article