Most Shopify stores I audit have the same pattern: three or four apps all trying to track the same purchase, a GA4 property that misses orders, and a Meta account that reports more sales than the store received. Shopify has made tracking more locked down with checkout extensibility, which is good for security but confusing for anyone who used to paste scripts on the thank-you page. This guide explains how I build Shopify server-side tracking today, using Customer Events for browser events and order webhooks for purchases, so every platform gets one clean, deduplicated stream.
What is Shopify server-side tracking?
Shopify server-side tracking means sending your store's events, especially purchases, to ad and analytics platforms from a server instead of relying only on the visitor's browser. The server can be server-side Google Tag Manager (sGTM) hosted on Stape or Google Cloud, which forwards events to the Meta Conversions API (CAPI), Google Ads, GA4 and the TikTok Events API.
On Shopify, a solid setup has two layers:
- Browser events (page views, product views, add to cart, checkout steps) captured with Shopify Customer Events and sent to your web and server GTM containers.
- Purchases from Shopify's backend, sent by order webhooks to your server container, so a purchase is recorded even when the browser never loads the thank-you page or a blocker stops the script.
If you are new to the concept, start with what server-side tracking is, then come back here.
What does checkout extensibility change for tracking?
Checkout extensibility means you can no longer run your own scripts directly on Shopify's checkout and thank-you pages. Shopify retired checkout.liquid and the old "Additional scripts" box on the order status page, and moved tracking into Settings > Customer events, where pixels run in a sandbox.
What that means in practice:
- Custom pixels run in an isolated sandbox. They can subscribe to Shopify's standard events, but they cannot freely read or change the page around them.
- GTM can run inside a custom pixel, but its
dataLayerlives inside the sandbox, and the normal GTM Preview mode does not connect to checkout the way it does on a theme page. Test with the browser console and your server container's Preview mode instead. - App pixels from the Facebook & Instagram, Google & YouTube and TikTok apps also run through Customer Events, and some of them already send server events. This is the main source of duplicates.
How does the setup work end to end?
The setup works by sending browser events through a custom pixel to sGTM, and sending each paid order from a Shopify webhook to the same server container, with the order ID as the shared event ID. Here is the architecture I use most:
| Layer | Source | Events | Destinations |
|---|---|---|---|
| Browser | Customer Events custom pixel + web GTM | page_viewed, product_viewed, product_added_to_cart, checkout_started, checkout_completed |
GA4, Meta Pixel, TikTok Pixel, Google Ads (via sGTM) |
| Server | sGTM on a first-party subdomain (Stape) | Browser events forwarded with server enrichment | GA4, Meta CAPI, TikTok Events API, Google Ads |
| Backend | Shopify order webhook (orders/paid or orders/create) |
Purchase | Meta CAPI, TikTok Events API, GA4 Measurement Protocol |
Step 1: Map a first-party tracking subdomain
Create your server container in GTM, host it on Stape (or Google Cloud Run), and map a subdomain such as track.yourstore.com to it with a DNS record. A first-party subdomain helps cookies and requests be treated as belonging to your store. Stape's Shopify app can also help send the data layer and webhook data to the container, which saves a lot of custom code.
Step 2: Add a custom pixel for browser events
In Settings > Customer events, add a custom pixel that subscribes to Shopify's standard events and pushes them into GTM. A simplified purchase subscription looks like this:
analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: 'purchase',
event_id: String(checkout.order.id),
ecommerce: {
transaction_id: String(checkout.order.id),
value: checkout.totalPrice.amount,
currency: checkout.currencyCode
}
});
});
Map the other standard events to GA4 ecommerce names (view_item, add_to_cart, begin_checkout) with item arrays. My GA4 ecommerce checklist lists the parameters each event needs, and the GA4 dataLayer generator gives you correctly shaped pushes to start from.
Step 3: Capture browser identifiers for the webhook
Order webhooks come from Shopify's servers, so they do not carry the visitor's browser cookies. To attribute a webhook purchase, you need identifiers such as _fbp, _fbc (Meta), _ttp and ttclid (TikTok), the GA4 client_id and session ID, and the Google click ID (gclid).
There are two common ways to connect them to the order:
- Store them against the cart. A small storefront script or an app writes the identifiers into cart attributes, which appear on the order as note attributes in the webhook payload.
- Store them in a server-side lookup. The browser sends the identifiers to sGTM with the cart or checkout token, and the server container looks them up by that token when the webhook arrives. Stape offers a store and lookup feature built for this.
Step 4: Send purchases from an order webhook
Create a webhook for the order topic you want and point it at your server container's URL (or let the Stape app manage it). Then build server tags that turn the webhook into purchase events for each platform.
Which topic to use matters:
orders/paidfires when the order is marked paid. It suits stores where payment is captured at checkout.orders/createfires when the order is created. For cash on delivery stores, which are very common in Bangladesh,orders/paidmay only fire days later when the courier collects payment, soorders/createis usually the better trigger for ad optimization.
Also filter out orders you do not want to report, such as point-of-sale, draft or test orders, and verify the webhook's HMAC signature header so nobody can post fake orders to your endpoint.
How do you deduplicate browser and server purchases?
You deduplicate by sending the same event name and the same event ID from both the browser and the webhook, using the Shopify order ID as that ID. Meta and TikTok both use the event ID to merge a Pixel event with its server event, so the purchase is counted once. Make sure both sides use the same format: if the pixel sends 1234567890 and the webhook sends #1001 (the order name) or a Shopify GID, they will not match.
Platform by platform:
- Meta:
eventIDin the Pixel andevent_idin CAPI must match. My guide to Meta CAPI event deduplication covers the testing process. - TikTok: use the same
event_idin the TikTok Pixel and the Events API. I cover the full setup in TikTok Pixel and Events API setup. - GA4: I usually pick one purchase source, either the browser event through sGTM or the webhook through Measurement Protocol, rather than both. Always send
transaction_id, and when using the webhook, include the storedclient_idandsession_idso the purchase attaches to the right session. - Google Ads: the conversion needs a click ID or hashed customer data to be attributed. I normally send the purchase from the browser through sGTM with enhanced conversions, and use the webhook data as a backup check rather than a second conversion.
Turn off duplicate app tracking
This is the step people skip. If the Facebook & Instagram app is set to share data at a level that includes server events, and your sGTM setup also sends CAPI purchases to the same Pixel, the two will use different event IDs and double-count. The same applies to the Google & YouTube app sending GA4 and Google Ads conversions alongside your GTM tags. Choose one source per platform and disable the others in the app settings or under Customer events.
How do you test Shopify server-side tracking?
You test it by placing real orders and following each event from Shopify through sGTM to every platform. My checklist:
- Server container Preview mode shows the webhook request arriving and each server tag returning success.
- Meta Test Events shows the purchase from browser and server with the same event ID.
- TikTok Events Manager shows the purchase without duplicate warnings.
- GA4 DebugView and later the monetisation reports show one purchase with the correct transaction ID and value.
- Google Ads conversion diagnostics show the purchase with enhanced conversions data.
- Over the following weeks, platform purchase counts are compared with Shopify orders for the same dates.
On Modalclo, a Shopify fashion brand, I replaced a browser-only Pixel with deduplicated Pixel and Conversions API events using GTM and Stape, plus GA4 enhanced ecommerce mapped to Shopify product and checkout data. For a broader look at app-based options, see Meta Conversion API on Shopify and WooCommerce.
Final thoughts
The best Shopify server-side tracking setups are not the ones with the most apps; they are the ones where each platform receives one clean purchase with a shared order ID. Use Customer Events for browser signals, webhooks for reliable purchases, and switch off overlapping integrations. If you want this built properly, see my Shopify conversion tracking page and advanced conversion tracking service, or book a free tracking audit.

