Shopify
Shopify needs two pieces: the snippet in the theme (storefront pages) and a custom pixel for checkout and thank-you pages, which themes cannot reach.
Install
*Online Store → Themes → … → Edit code → layout/theme.liquid*, inside <head>:
<script>window.x3=window.x3||function(){(window.x3q=window.x3q||[]).push(arguments)}</script>
<script async src="{{API_ORIGIN}}/v1/s/x3.full.js?k={{PUBLISHABLE_KEY}}" data-key="{{PUBLISHABLE_KEY}}"></script>Settings → Customer events → Add custom pixel, name it "3x", permission Analytics, and paste:
// 3x custom pixel – runs in Shopify's pixel sandbox (no DOM of the shop page, no storage).
const KEY = "{{PUBLISHABLE_KEY}}";
const ENDPOINT = "{{API_ORIGIN}}/v1/e";
const sessionId = crypto.randomUUID();
// One batch (wire format v2) per checkout event: a pageview of a fixed checkout path plus the event.
function send(path, name, props) {
const view = init.context.window;
const pageview = {
id: crypto.randomUUID(), seq: 1, age: 0, nav: "load",
host: init.context.document.location.hostname, path, variant: null, lang: null, ref: null,
attrFrom: "none", attr: {},
vw: view.innerWidth, vh: view.innerHeight, sw: view.innerWidth, tp: false,
ph: 0, sy: 0, sp: 0, marks: {}, sec: {}, eng: 0, clk: 0, iclk: 0, vids: [],
};
const batch = {
v: 2, k: KEY, sid: sessionId, fresh: true, consent: false, test: false,
pvs: [pageview], ev: [{ pv: pageview.id, n: 0, t: 0, e: name, p: props }], lib: "x3-shopify-pixel@1",
};
fetch(ENDPOINT, {
method: "POST", keepalive: true, credentials: "omit",
headers: { "content-type": "text/plain;charset=UTF-8" },
body: JSON.stringify(batch),
}).catch(() => {});
}
analytics.subscribe("checkout_started", (event) => {
send("/checkout", "checkout_started", { items: event.data.checkout.lineItems.length });
});
analytics.subscribe("checkout_completed", (event) => {
const checkout = event.data.checkout;
send("/checkout/thank-you", "checkout_completed", { value: Number(checkout.totalPrice.amount), currency: checkout.currencyCode });
});Each checkout event is recorded as a pageview of a fixed path (/checkout, /checkout/thank-you),
never of the tokenized checkout URL. The server links it to the visitor's storefront session through
the daily visitor hash. App pixels (strict sandbox) use the DOM-free transport of
@3x-engine/sdk/pixel, which builds the same batches.
Verify
- Visit the storefront in a private window;
POST {{API_ORIGIN}}/v1/e→ 204. - Customer events → 3x → Test opens the pixel helper; place a test order with a bogus gateway.
- The dashboard shows
checkout_started/checkout_completedin the event catalog.
Add your shop domains (YOUR-STORE.myshopify.com and the custom domain) to the project's domains and
allowed origins – the pixel reports the shop's host.
Proxy (optional)
Shopify themes cannot host server routes; use an app proxy of a custom app or a Cloudflare Worker on your custom domain, then point the snippet at it:
<script async src="{{PROXY_PATH}}/v1/s/x3.full.js?k={{PUBLISHABLE_KEY}}" data-key="{{PUBLISHABLE_KEY}}" data-host="{{PROXY_PATH}}"></script>Consent
Shopify's Customer Privacy API decides for pixels automatically (pixels with Analytics permission run only when allowed and replay events after consent). For the storefront snippet, forward the decision:
<script>
document.addEventListener("visitorConsentCollected", function (event) {
x3("consent", event.detail.analyticsAllowed ? "granted" : "denied");
});
</script>Events
<button type="submit" name="add" data-x3-event="add_to_cart" data-x3-prop-product="{{ product.handle }}">Add to cart</button>Register checkout_completed with a value prop and use it as a goal with a value to get revenue
per channel and ROAS on the ads report.