End-to-end attribution for two Shopify stores: from click to order
A US outdoor-gear brand was paying for Meta, Google and YouTube but could not tell which ads produced orders. I built a continuous "ad click → order → ad account" data path on the Keitaro tracker plus a custom middleware. Revenue reports stopped disagreeing with reality, and the owner finally had grounds to reallocate the ad budget.
Three sources of numbers, and none of them agreed
The business was spending on Meta, Google and YouTube and could not answer the very first
question: which ads bring orders. The Shopify storefront attributed a large share of sales to
Direct,
the ad accounts reported conversions that did not match the owner's operational spreadsheet,
and the spreadsheet matched neither of the other two.
The business owner put it bluntly: "the data doesn't line up". That is the worst possible state — not the absence of analytics, but analytics nobody trusts. When three reports produce three different revenue figures, every meeting turns into an argument about numbers instead of a conversation about advertising.
The practical consequence: budget decisions were made on gut feel. Nobody could say what one sale cost on each channel — so there was no way to switch off a losing channel or deliberately double down on a profitable one. Money was allocated out of habit.
A continuous data path: the click is never lost
Attribution doesn't break in one place, it breaks in four: the click tag never reaches the storefront, the webhook is delivered twice, the server-side event duplicates the browser one, and ad spend never lands in the report. So the deliverable wasn't "a pixel" but an unbroken path where every link can be verified on its own.
Click tag captured in the store theme, mirrored by a custom vid
cart / checkout / order → http.server middleware, per-store HMAC, deduplication
S2S to the tracker: lead per unique click, sale with revenue per order
SQLite graph: ksubid / e-mail hash / checkout_token → back-link to the first click
Multi-touch paths, channel credit and role, LTV, attribution wins
The click tag had to be captured in the theme itself
The standard route — Web Pixel — did not work: inside the Shopify sandbox it does not write
cart attributes,
so the click tag simply never reaches the order. The tag is therefore captured in the store
theme and mirrored by a custom visit identifier
(vid),
so the click↔order link survives even where the built-in mechanism drops it.
Middleware: per-store HMAC, deduplication at the door
A custom middleware built on
http.server
receives Shopify webhooks and does four things:
- verifies the
HMACsignature — with a separate secret for each of the two stores - deduplicates events: Shopify retries delivery, and the report must not grow because of it
- fires an S2S postback to the tracker —
leadper unique click,salewith revenue per order - writes every funnel event to its own
JSONLlog — the granular path is preserved where the tracker only aggregates
Conversions API: events collapse instead of doubling
In parallel with the tracker postback, the middleware sends a server-side
Purchase
to the Facebook Conversions API — carrying the same
event_id
as the browser pixel. This is the detail that matters: without a shared identifier you don't get
recovered attribution, you get doubled conversions in the ad account and optimization against
invented numbers.
An identity graph instead of last-click
A separate SQLite database stitches click, customer and order together via
ksubid,
e-mail hash and
checkout_token,
with a back-link to the first click. On top of that graph sits a reporting layer that answers
questions last-click cannot:
- the buyer's multi-touch path and credit distribution across channels
- the channel's role: opener / assist / closer
- LTV by channel — not just first-order revenue
- "attribution wins": the storefront said
Direct, the tracker caught a paid source - a join with the post-purchase "how did you hear about us" survey — an independent check on the model
Ad spend syncs daily — otherwise ROI is computed without costs
Half of all attribution projects fail on something mundane: revenue per channel exists, but the spend stays inside the ad account. Campaign spend is pulled on a schedule from the Google Ads API and the Meta Marketing API straight into the tracker, so cost per sale by channel is a field in the report rather than a monthly manual reconciliation.
Standard library only
The whole path is written with
urllib,
http.server
and
sqlite3
— not a single external dependency. The reason is prosaic: the code has to run on a production
server with no pip.
The side effect is that there is nothing to rot and nothing to patch on a security alert.
Zero dependencies — because the production box has no pip
http.server, urllib, sqlite3 — no external dependencies at all
OAuth app, cart / checkout / order webhooks, per-store HMAC
S2S postbacks (lead / sale) and Admin API for campaigns and spend
Server-side Purchase with a shared event_id — deduplicated against the browser pixel
Daily sync of campaign spend into the tracker
Cross-check of sessions and sources against first-party data
Identity graph click↔customer↔order with a back-link to the first click
Click tag capture in the store theme plus a custom vid
Middleware as a service, syncs on timers, restarts without manual work
Granular funnel in first-party storage, independent of tracker aggregates
The report stopped lying — and immediately changed decisions
phantom revenue disappeared once the calculation was fixed
cancelled orders counted as revenue (+25.7%) and discount codes never subtracted (+5.74%)
of one store's web revenue — root cause found and confirmed
Two calculation defects that were destroying trust in the report
The first thing the data path delivered was the ability to reconcile the report against reality — and that reconciliation immediately surfaced two bugs. Cancelled orders were counted as revenue: on one day that inflated the figure by 25.7% and, worse, presented a cancelled order as an "attribution win". Discount codes were never subtracted — another 5.74% on top. Both are fixed; the daily report no longer diverges from actual revenue.
Cost per sale by channel changed the budget decision
Once campaign spend and tracked sales met in the same report, the picture was uncomfortably clear: roughly 186k clicks from Facebook produced not a single tracked sale, while every tracked sale that month came from YouTube on about 12k clicks. On that evidence the business owner switched off the Facebook leg and reallocated the budget. That is exactly what attribution is built for — not a good-looking dashboard, but one management decision that cannot be made without numbers.
A third of the revenue with no source turned out not to be a tracking bug
About a third of one store's web revenue arrived with no source — and the right answer here was not "fix the pixel". The cause was external: the cookie consent banner blocked the Shopify session until the visitor clicked "Accept", so the source was lost before analytics ever got a turn. The diagnosis was confirmed by an A/B comparison of the two storefronts and by a historical precedent on the second store — not by guesswork.
The overall outcome, stripped of detail: the business now has a single source of truth that holds up under scrutiny. The report can be questioned again — and the answer no longer has to be re-checked by hand.
Where else the same methodology applies
This case isn't about Shopify. It's the standard problem of "stitch a site event to the money in the till and to the spend in the ad account". The same path transfers almost unchanged anywhere there is paid traffic and orders:
- → Any store on Shopify / WooCommerce / a hosted builder where the storefront reports suspiciously much "Direct"
- → Multiple storefronts or domains on one shared ad budget — a single attribution model instead of scattered ad accounts
- → Lead-based businesses with a long cycle — enquiry, call, CRM deal: the same "click → identifier → S2S postback" chain
- → Server-side events for Meta / Google / TikTok after browser tracking loss — with mandatory event_id deduplication
- → Audit of existing analytics where reports exist but contradict each other — often that's a calculation defect, not a tracking one
- A stdlib middleware template: webhook intake, per-store HMAC, deduplication, S2S postback
- The shared event_id scheme for browser pixel and Conversions API — conversions never double-count
- SQLite identity graph: click ↔ customer ↔ order with a back-link to the first touch
- Daily spend sync from the ad APIs — ROI computed with costs in it, not without them
- The reporting layer on top of the tracker: multi-touch, channel credit and role, LTV, attribution wins
If your storefront says "Direct" and your reports contradict each other — that's fixable
The right starting point isn't building, it's auditing the path: where exactly the tag is lost, where the event is duplicated, and where the report computes something other than what it shows. The attribution core takes 2-3 weeks; reporting and tuning on real data follow.
The 5,000 ₽ audit — with a concrete report and quote
I'll tell you what to deploy in your business first, what the payback looks like, and whether you need AI for the task at all (sometimes you don't).
Or just send your question — I reply within 2 hours