Tracking and Consent
How the app captures and attributes checkouts through the order webhook and the web pixel, why both exist, and what a shopper's consent choice changes
Copy this guide and your current settings as a prompt for your AI agent.
Preview agent promptSee the guide, code and settings included when you copy.
Two independent paths carry a Shopify checkout to Coframe. Both exist because each covers a case the other cannot, and since September 2026 the sale is captured for every shopper, whether or not they allowed analytics.
On this page
01Two paths to Coframe
| Path | Runs where | Trigger | Consent |
|---|---|---|---|
| Order webhooks | The Coframe app server | Shopify’s checkouts/create, checkouts/update, orders/create |
Independent: server to server, never in the shopper’s browser |
| Web pixel | Shopify’s pixel sandbox in the shopper’s browser | checkout_started, checkout_completed |
Loads for every shopper; what it sends depends on the shopper’s analytics consent |
The webhook forwards the order to the Coframe Conversion API with the
store’s conversion API key. The pixel posts straight to Coframe ingest; the
app server is not on that path. Both carry the same deduplication key,
shopify:<orderId> (or shopify:<cart_token> for a checkout in progress), so
one purchase that arrives twice is counted once at query time.
02The visitor token
Attribution needs the visitor’s Coframe token, the same coframe.user.token
cookie the SDK uses everywhere. The app’s helper script writes it into the
cart as the attribute __coframe_user_token on every page load. Shopify
carries cart attributes into the checkout and into the order:
| Surface | Where it reads the token |
|---|---|
| Web pixel | checkout.attributes.__coframe_user_token, populated by Shopify, so it works on any checkout origin including Shop Pay; cookie and local storage are narrow fallbacks |
| Order webhook | note_attributes on the order payload (or the cart attributes on an in-progress checkout) |
Two outcomes are kept separate throughout: captured means the event, SKU and revenue reached Coframe; attributed means that event also carries the visitor token and can be tied to an experiment.
03Why both paths
Dynamic checkout buttons (Buy it Now, Shop Pay, Google Pay, Apple Pay) create
a fresh server-side cart that never received the cart attribute, so the order
arrives without a token and the webhook alone cannot attribute it. The pixel
can, because Shopify populates checkout.attributes from the visitor’s
earlier cart writes regardless of which cart produced the order.
| Buyer segment | Webhook | Pixel | Outcome |
|---|---|---|---|
| Add to cart → checkout, analytics allowed | Token present | Loads, token present | Captured and attributed, deduplicated on the order id |
| Add to cart → checkout, analytics declined | Token present | Loads, sends the sale without identity | Captured and attributed by the webhook |
| Buy it Now / Shop Pay, analytics allowed | Token usually absent | Loads, token present | Captured and attributed by the pixel |
| Buy it Now / Shop Pay, analytics declined | Token usually absent | Loads, sends the sale without identity | Captured, not attributed |
04What consent changes
The pixel declares no required privacy purposes, so Shopify runs it for every
visitor. Inside the sandbox it reads the shopper’s live consent state from
Shopify’s Customer Privacy API and keeps listening for changes. Every event it
sends carries that state in metadata.consent.
- Analytics allowed: the storefront helper reuses the cookie or mints a token, and the pixel sends the full event.
- Analytics declined: the helper reuses a readable cookie but never mints one; the pixel keeps the order id (needed for deduplication), the SKUs and the revenue, and drops customer identity and behaviour. An empty token never blocks capture.
Read consent from metadata.consent, never from the token’s presence or
shape. The previous behaviour, where the pixel did not load at all for a
shopper who declined analytics, is why Buy it Now orders from those shoppers
produced no row at all; that gap is closed.
05Debugging which channel won
Every pixel post carries metadata.user_token_source
(checkout_attributes, cookie or localStorage), visible in the Network
panel on the checkout page. No pixel post at all means the pixel skipped the
send, and its sandbox console says why. A purchase present from the webhook
but without a token is a dynamic checkout from a shopper the pixel could not
identify; it counts as revenue, not as an experiment conversion.
06Where in the code
| Place | What to look for |
|---|---|
sdk/app-shopify/extensions/coframe-pixel |
The web pixel: manifest privacy declaration, consent subscription, event posts |
sdk/app-shopify/extensions/coframe-embed |
The theme app embed that serves the SDK and helper script |
sdk/app-shopify/app |
Dashboard, connect flow, webhook forwarder, pixel registration |
sdk/docs/shopify-app.md |
The full architecture document with the coverage matrix, token propagation and pixel lifecycle |
# Tracking and Consent How the app captures and attributes checkouts through the order webhook and the web pixel, why both exist, and what a shopper's consent choice changes Two independent paths carry a Shopify checkout to Coframe. Both exist because each covers a case the other cannot, and since September 2026 the sale is captured for every shopper, whether or not they allowed analytics. [Video: Video: the consent-aware change: the pixel loads for every shopper, the sale is always captured, and existing installs need no action (106s, narrated)](assets/videos/shopify-app-consent-tracking.mp4) Watch: 106s — the consent-aware change: the pixel loads for every shopper, the sale is always captured, and existing installs need no action ## Two paths to Coframe Path | Runs where | Trigger | Consent | Order webhooks | The Coframe app server | Shopify’s `checkouts/create`, `checkouts/update`, `orders/create` | Independent: server to server, never in the shopper’s browser | Web pixel | Shopify’s pixel sandbox in the shopper’s browser | `checkout_started`, `checkout_completed` | Loads for every shopper; what it sends depends on the shopper’s analytics consent | The webhook forwards the order to the Coframe Conversion API with the store’s conversion API key. The pixel posts straight to Coframe ingest; the app server is not on that path. Both carry the same deduplication key, `shopify:<orderId>` (or `shopify:<cart_token>` for a checkout in progress), so one purchase that arrives twice is counted once at query time. ## The visitor token Attribution needs the visitor’s Coframe token, the same `coframe.user.token` cookie the SDK uses everywhere. The app’s helper script writes it into the cart as the attribute `__coframe_user_token` on every page load. Shopify carries cart attributes into the checkout and into the order: Surface | Where it reads the token | Web pixel | `checkout.attributes.__coframe_user_token`, populated by Shopify, so it works on any checkout origin including Shop Pay; cookie and local storage are narrow fallbacks | Order webhook | `note_attributes` on the order payload (or the cart attributes on an in-progress checkout) | Two outcomes are kept separate throughout: captured means the event, SKU and revenue reached Coframe; attributed means that event also carries the visitor token and can be tied to an experiment. ## Why both paths Dynamic checkout buttons (Buy it Now, Shop Pay, Google Pay, Apple Pay) create a fresh server-side cart that never received the cart attribute, so the order arrives without a token and the webhook alone cannot attribute it. The pixel can, because Shopify populates `checkout.attributes` from the visitor’s earlier cart writes regardless of which cart produced the order. Buyer segment | Webhook | Pixel | Outcome | Add to cart → checkout, analytics allowed | Token present | Loads, token present | Captured and attributed, deduplicated on the order id | Add to cart → checkout, analytics declined | Token present | Loads, sends the sale without identity | Captured and attributed by the webhook | Buy it Now / Shop Pay, analytics allowed | Token usually absent | Loads, token present | Captured and attributed by the pixel | Buy it Now / Shop Pay, analytics declined | Token usually absent | Loads, sends the sale without identity | Captured, not attributed | ## What consent changes The pixel declares no required privacy purposes, so Shopify runs it for every visitor. Inside the sandbox it reads the shopper’s live consent state from Shopify’s Customer Privacy API and keeps listening for changes. Every event it sends carries that state in `metadata.consent`. - Analytics allowed: the storefront helper reuses the cookie or mints a token, and the pixel sends the full event. - Analytics declined: the helper reuses a readable cookie but never mints one; the pixel keeps the order id (needed for deduplication), the SKUs and the revenue, and drops customer identity and behaviour. An empty token never blocks capture. Read consent from `metadata.consent`, never from the token’s presence or shape. The previous behaviour, where the pixel did not load at all for a shopper who declined analytics, is why Buy it Now orders from those shoppers produced no row at all; that gap is closed. ## Debugging which channel won Every pixel post carries `metadata.user_token_source` (`checkout_attributes`, `cookie` or `localStorage`), visible in the Network panel on the checkout page. No pixel post at all means the pixel skipped the send, and its sandbox console says why. A purchase present from the webhook but without a token is a dynamic checkout from a shopper the pixel could not identify; it counts as revenue, not as an experiment conversion. ## Where in the code Place | What to look for | `sdk/app-shopify/extensions/coframe-pixel` | The web pixel: manifest privacy declaration, consent subscription, event posts | `sdk/app-shopify/extensions/coframe-embed` | The theme app embed that serves the SDK and helper script | `sdk/app-shopify/app` | Dashboard, connect flow, webhook forwarder, pixel registration | `sdk/docs/shopify-app.md` | The full architecture document with the coverage matrix, token propagation and pixel lifecycle |