Tracking and attribution
Preserve candidate ownership, record valid impressions and clicks, and add the private Shopify line properties required for attributable orders.
Reliable attribution depends on one uninterrupted identity chain:
resolve candidate → rendered card → shopper action → Shopify cart line → order webhook
Every step must keep the selected candidate’s Campaign, Rule, request, and tracking identity together.
Tracking tokens are HMAC-signed by the server and bind the shop, Campaign, Rule, candidate product, and request. Event ingest rejects an event, per item with invalid_tracking_token, when its token was not issued for that card or is more than 24 hours old. Never construct or reuse a token in the browser.
Valid impressions
An impression is valid after the same card is at least 50% visible for at least 500 milliseconds while the page is visible. A tracking token records at most one valid impression in a mount generation.
client.mount() handles this automatically for server-rendered cards. For a fully custom renderer, use the SDK’s createImpressionObserver and pass one target for each rendered candidate. Do not count a card when it is merely inserted into the DOM.
Each impression includes the resolved Shopify ProductVariant GID in attrs.variant_id when it is known.
Clicks
Track a click when the shopper activates the recommended product link or another action that opens its detail. Use the card’s own identity, not a response-level default:
client.track({
event_type: 'click',
tracking_token: card.trackingToken,
candidate_product_id: card.productId,
request_id: card.requestId,
campaign_id: card.campaignId,
rule_id: card.ruleId,
placement: card.placement,
attrs: { variant_id: card.variantId },
});
Add to Shopify cart
Write all four private properties on the exact recommended line. The leading underscore keeps them out of the normal shopper-facing cart presentation in compatible themes.
const response = await fetch('/cart/add.js', {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
items: [
{
id: numericVariantId,
quantity: 1,
properties: {
_tab_tracking_token: card.trackingToken,
_tab_request_id: card.requestId,
_tab_campaign_id: card.campaignId,
_tab_rule_id: card.ruleId,
},
},
],
}),
});
if (response.ok) {
client.track({
event_type: 'add_to_cart',
tracking_token: card.trackingToken,
candidate_product_id: card.productId,
request_id: card.requestId,
campaign_id: card.campaignId,
rule_id: card.ruleId,
placement: card.placement,
attrs: {
variant_id: card.variantId,
quantity: 1,
},
});
}
Shopify’s Ajax Cart API expects a numeric variant ID. If your recommendation state stores a Variant GID such as gid://shopify/ProductVariant/123, extract and validate the final numeric segment before the cart request.
Track add_to_cart only after Shopify returns success. A button click, optimistic UI state, or failed cart request is not an add-to-cart event.
Event identity and retry
The SDK generates event_id and occurred_at when you omit them. If delivery fails, the queued event keeps the same values on retry, so the server can treat replay as idempotent.
The queue:
- sends at most 100 events per request;
- uses
keepalivefor navigation and page-hide delivery; - flushes on an interval, queue threshold, visibility change, and page hide;
- keeps retryable network and server failures queued with bounded backoff;
- never allows delivery failure to block product, cart, or checkout actions.
Call await client.flush() before a controlled navigation when practical, then call client.destroy() during teardown.
Partial event rejection
The event endpoint can accept valid items and reject invalid items in the same batch:
{
"data": {
"accepted": 2,
"rejected": [
{
"index": 2,
"event_id": "01900000-0000-7000-8000-0000000000aa",
"code": "invalid_event_type",
"reason": "event_type is not supported"
}
]
}
}
A rejected item is final and should be fixed in the integration. Do not retry a permanently invalid event forever.
Verification in analytics
Before launch, complete one test path for each enabled placement:
- render a known recommendation;
- hold it at least 50% visible for 500 milliseconds;
- click the recommended product;
- add the exact variant to cart;
- confirm the four private line properties in
/cart.js; - complete a test order if the store’s test setup permits it;
- confirm the events and resulting attribution appear under the same Campaign and Rule.
If the events appear but the order does not attribute, inspect the Shopify line properties first. If the properties exist but identities differ across events, inspect where the card’s data was copied or remapped.