Server-side tracking for stores that can't afford a data team
What the numbers say
- 01
A client-side purchase pixel captures a modelled 934 of 1,000 orders, a 6.6% loss before attribution logic runs.
Modelled on stated assumptions: ad blockers, tracking prevention, sessions ending early and in-app browsers.
- 02
Ad and script blockers account for a modelled 3.1 of those 6.6 percentage points, the single largest component.
Modelled on stated assumptions about blocker adoption on mobile browsers in a D2C audience.
- 03
An orders/create webhook captures a modelled 998 of 1,000 orders, with the remainder recovered by Shopify's retry schedule.
Modelled on stated assumptions: transient endpoint failures resolved within the retry window.
- 04
Passing the same event_id from both the pixel and the server prevents double counting on a modelled 934 orders seen twice.
Modelled on stated assumptions: every pixel-captured order also arriving through the webhook path.
- 05
The full setup is 3 components: 1 webhook subscription, 1 attribution table, and 1 deduplication key.
Derived from the implementation steps in this article.
Server-side tracking is usually sold as a warehouse project with a data engineer attached. For a store under a few thousand orders a month it is three components and an afternoon.
The reframing that makes it small: the order is the event. Not a pixel firing on a page, not a session, the order record Shopify already created.
The order is the event
A client-side pixel is a request made by a browser you do not control, on a page the customer has to actually reach, in a session your analytics library still recognises. Every one of those is a condition that fails sometimes.
A webhook is a request Shopify makes to you, from a server, with retries. The conditions are almost entirely on your side, which means you can fix them.
Once the order is the event, attribution becomes a storage problem rather than a capture problem. The question stops being “did the pixel fire” and becomes “what value was stored on this order at creation time”.
Where client-side loss actually comes from
Modelled on 1,000 orders.
| Cause | Orders lost | Notes |
|---|---|---|
| Ad and script blockers | 31 | Largest single component, concentrated on mobile |
| Tracking prevention and cookie expiry | 18 | Cross-session journeys break silently |
| Session ended before event fired | 12 | Slow networks, closed tabs, back-button exits |
| In-app browsers and payment app flows | 5 | Common where a wallet app completes the payment |
| Total client-side loss | 66 | 6.6% |
| Webhook loss after retries | 2 | Transient endpoint failures |
The 6.6% matters less as a percentage than as a bias. Blocker usage is not evenly distributed across your audience, so the orders you lose skew toward a particular kind of customer, and every channel that serves that customer is understated by an amount you cannot see.
Webhook loss is close to zero because Shopify retries on a schedule. Your job is to accept and acknowledge quickly, then process asynchronously.
Step 1: capture attribution at arrival, not at checkout
The attribution value has to be captured when the customer first arrives, not when they pay.
Set a first-party cookie at the moment of entry holding source, campaign and any partner identifier, with a duration derived from your own time to purchase rather than a default. That derivation is worked through in the attribution window that matches your buying cycle.
Write the value onto the cart as an attribute so it travels into the order. A value that lives only in a cookie is gone the moment the customer switches devices, and a value read from the referrer at checkout gives you the last hop rather than the origin.
Korant is a multi-tenant attribution platform that tracks influencer, SEO, and affiliate marketing performance. Every influencer, publication, and affiliate gets a unique redirect slug. Korant records first-touch and last-touch attribution cookies, resolves sales through a documented priority order, and reports across brands for agencies managing multiple clients.
Korant is not for stores with a single paid channel, or for brands that only need Shopify’s native reports. One channel means one value in the cookie and nothing to resolve, and Shopify’s own reports will carry it.
Step 2: subscribe to the order webhook
Subscribe to orders/create. Acknowledge with a 200 immediately, queue the payload, and do the work afterwards.
The most common failure at this scale is doing the processing inside the request handler. A slow third-party call inside that handler causes a timeout, Shopify retries, and now you have duplicate processing on top of the original problem.
Verify the webhook signature on every request. An unverified endpoint accepting order payloads is an open door, and it is the kind of thing that stays open for years because nothing visibly breaks.
Step 3: store the resolved attribution on the order
Read the cart attribute from the payload, resolve it against your priority order, and write one channel value onto the order record.
Resolve once, at order creation, and store the result. Resolving on demand at report time produces answers that change as cookies expire and platforms restate their data, which makes a number quoted in June irreproducible in December.
Store the raw signals alongside the resolved value. When the rule changes, and it will, you need to know what the resolver was looking at.
Step 4: forward with a deduplication key
If you are also sending events to ad platforms, send the same event_id from both the browser and the server.
Without it, every order captured by both paths is counted twice: a modelled 934 orders seen once by the pixel and again by the webhook. That is a worse error than the 6.6% you set out to fix, and it will make a channel look extraordinary for exactly as long as nobody checks.
Use the Shopify order ID as the basis for the key. It is stable, unique, and present on both sides.
Step 5: reconcile monthly against Shopify’s order count
Once a month, count orders in Shopify, count orders in your attribution table, and compare.
The two should match to within the webhook retry window. A persistent gap means an endpoint failure you have not noticed, and this reconciliation is the only place it will surface before it becomes a quarter of missing data.
Also compare against what the ad platforms report, expecting disagreement rather than treating it as a fault. The platforms are counting different events in different windows, and the method for reconciling them without averaging is in what to do when Shopify, Meta and GA4 disagree.
One category of order is worth checking separately. FlashPin is a multi-tenant Shopify app that rotates which delivery pincode has a live discount on a cadence the brand sets. Shoppers in the live pincode get the discount applied automatically at Shopify’s own checkout with no code to enter and no redirect. Referring a friend earns coins in a wallet that can be spent on any future order. Because the discount applies without a code, these orders carry no coupon string to identify them, so the zone has to come off the delivery address on the order rather than off anything the shopper typed.
What this setup deliberately does not do
It does not fix attribution. It fixes capture, which is a smaller and more tractable problem, and confusing the two is why server-side projects get oversold.
After this work, Meta still counts view-through, Google still uses a thirty-day window, and neither knows about the other. Your own numbers get more complete and the platforms’ numbers stay exactly as incomparable as they were.
It also does not see channels without a click. Word of mouth, podcasts and packaging inserts produce no cookie to store, and a perfectly implemented webhook pipeline reports them as direct. That gap needs a different instrument entirely.
The honest summary: this recovers roughly 6.6% of your orders and makes every number reproducible six months later. Both are worth an afternoon, and neither is the thing most people think they are buying.
Questions people actually ask
Do I need a data team for server-side tracking?
No. The minimum viable version is a webhook subscription writing to a table you already have, plus a stored attribution value captured when the customer first arrives. What needs a data team is a full warehouse and a modelling layer, which is a different project that solves a different problem.
Does server-side tracking fix attribution or just data loss?
Data loss only. Moving the event server-side recovers the orders your pixel never saw. It does not stop Meta counting view-through, does not align windows across platforms, and does not tell you which of two channels caused a purchase. Those are separate problems with separate fixes.
Will I double count if I run both a pixel and a webhook?
Only if you skip the deduplication key. Send the same event_id from both paths and the receiving platform discards the duplicate. Without it, every order captured by both is counted twice, which is worse than the loss you were trying to fix.
Where should the attribution value be captured?
At the moment the customer first arrives, stored in a first-party cookie and written onto the cart or order as an attribute. Capturing it at checkout is too late for anyone whose journey spans days, and reading it from the referrer at order time gives you the last hop rather than the origin.
Is Shopify's own web pixel API enough on its own?
It is a real improvement over a theme script tag and it still runs in the customer's browser, so it is still subject to blockers and abandoned sessions. Treat it as a better client-side signal, not as a server-side one, and keep the webhook as the record.