The constraint
Shopify's checkout and thank-you pages are hosted by Shopify. A script in theme.liquid runs on the storefront but not there, so a theme-only setup misses the purchase or relies on the deprecated additional-scripts box. Two things replace it:
- A web pixel extension — sandboxed code Shopify runs on every page including checkout, with access to standard customer events and the Customer Privacy API.
- Order webhooks — Shopify posts the order to your endpoint when it is paid, signed with a secret only you and Shopify know.
Track ships both: integrations/shopify/web-pixel and a verified receiver in the collector.
The web pixel
The pixel subscribes to page_viewed, product_viewed, product_added_to_cart, checkout_started, payment_info_submitted and checkout_completed, maps them to the standard events and posts them to the collector's browser endpoint. It reads consent from Shopify's Customer Privacy API: analyticsProcessingAllowed becomes the analytics purpose, marketingAllowed the marketing purpose, and a visitorConsentCollected update changes later events. Nothing is mirrored to vendor tags from inside the sandbox; vendors receive events server-side through the configured destinations.
The checkout_completed event carries the Shopify order id. That id is what makes the next step honest.
The verified webhook
In Shopify admin, Settings → Notifications → Webhooks, you create orders/paid and refunds/create pointing at the connection's webhook URL — a URL unique to your site with an unguessable token. Shopify signs every delivery with X-Shopify-Hmac-Sha256, the base64 HMAC-SHA256 of the raw body under the signing secret shown below the webhook list. You store that secret in Track's vault; the collector recomputes the HMAC, compares in constant time and also checks X-Shopify-Shop-Domain against the connected domain.
An accepted orders/paid becomes a purchase with source: shopify and source_verified: true: order id, currency, totals, tax, shipping, discount code, line items with variant ids, and the customer's e-mail, phone and address as raw matching data that the router hashes. orders/create is accepted only if the order is already paid; refunds/create becomes a refund with the refunded transaction amounts. Redeliveries map to the same deterministic event id and are dropped by the dedup guard.
Pairing by order id
The webhook knows nothing about consent. The pixel's purchase does. When the server purchase arrives, the router looks for a browser purchase with the same order id from the last 30 days and, if it finds one, the server event inherits its consent record, anonymous id, click ids and hashed identifiers, with provenance marking the consent as derived from the browser event. The verified server record then replaces the browser observation in the conversion table, and both events are routed: vendors that deduplicate on event id receive purchase:<order id> from both paths and count once.
If no browser purchase exists — the customer declined, or blocked the pixel — the server purchase is stored as an operational record and reaches only destinations that need no consent. It is never sent to advertising platforms on the assumption that a paying customer must have consented.
Setting it up
- Site → Shop connection → Shopify: enter
your-store.myshopify.com, save, copy the webhook URL. - Create the two webhooks in Shopify admin, JSON format; copy the signing secret into the connection.
- Deploy the web pixel extension with your tracking id (and a first-party collector host if you use one).
- Place a test order. The connection shows connected after the first verified webhook; the event debugger shows the pixel purchase and the shopify purchase with the same order id, and the destination monitor shows one delivery per destination and path.
What to check monthly
- Signature failures in the connection's last error: a rotated secret in Shopify without updating the vault.
- Orders with a server purchase but no browser purchase: the share tells you how many customers decline or block, and it is the ceiling for advertising attribution.
- Refund coverage:
refunds/createmust be subscribed, or vendor values drift upward.