Assignment proxy
The storefront runtime asks this Shopify app-proxy endpoint which variant a visitor should see; the response drives every test type.
This page is informational. In almost every case you should call window.abtest, which wraps this endpoint for you. Reach for the raw request only when you are debugging or building a custom integration.
What it does
The assignment endpoint takes an anonymous visitor id and returns the visitor's variant for every live experiment on the shop. The app embed calls it once per page and then applies the results — a redirect, a template or theme parameter, or a signal your JavaScript reads through window.abtest.
It is served under the Shopify app proxy, so it lives on the merchant's own storefront domain rather than on the app host. A typical mounted path looks like /apps/ab/assign, where /apps/ab is the proxy prefix Shopify forwards to the app. The visible path depends on the merchant's app-proxy configuration, so treat /apps/ab/assign as an example, not a fixed URL.
Why it runs through the app proxy
Routing through the Shopify app proxy — instead of a cross-origin API on the app host — gives the storefront runtime three properties it depends on:
- Same-origin requests. The call is first-party to the storefront, so there is no CORS preflight on the critical path.
- First-party cookies. Cookies set on the storefront origin survive Safari's Intelligent Tracking Prevention.
- A signed shop. Shopify adds an HMAC signature the request is authenticated against, so the shop identity cannot be forged by a client.
Request
The endpoint responds to a GET request forwarded by the Shopify app proxy. Shopify appends its own signed parameters (including shop and the HMAC signature); the runtime supplies the rest as query parameters.
| Parameter | Required | Meaning |
|---|---|---|
vid | Yes | The anonymous visitor id used for bucketing. Must be 8 to 128 characters. |
preview | No | Forces one experiment to a chosen variant for merchant or QA preview, bypassing bucketing. Validated against live experiments, so a stale value is ignored. |
activate | No | An experiment id. Enrols the visitor in a manual-trigger test they have now reached (see below). |
shop | Added by Shopify | The shop domain, taken from the proxy-authenticated session when present. |
Targeting inputs — device, new vs returning, country, UTM parameters, referrer, and cookies — are read from the forwarded request itself, not passed as explicit parameters. See audience targeting.
The request is authenticated as a Shopify app-proxy call. A request that does not carry Shopify's HMAC signature is rejected with 401, so you cannot exercise the endpoint by hitting it directly outside the proxy.
Response
The endpoint always returns JSON with Cache-Control: no-store. On success the body has three fields:
{
"assignments": [
{
"experimentId": "exp_123",
"type": "url",
"variantId": "var_b",
"deferred": false,
"variants": [
{ "id": "var_a", "isControl": true, "config": { ... } },
{ "id": "var_b", "isControl": false, "config": { ... } }
]
}
],
"clientIntegrations": [ ... ],
"preview": false
}
| Field | Meaning |
|---|---|
assignments | One entry per live experiment the visitor was assigned to. |
clientIntegrations | Enabled client-side providers the runtime should initialise. |
preview | true when a preview override applied; tells the runtime not to cache or track the visit. |
Assignment entry fields
| Field | Meaning |
|---|---|
experimentId | The experiment this assignment belongs to. |
type | The test type: URL, template, theme, or JavaScript. |
variantId | The variant assigned to this visitor. |
deferred | true when the bucket is computed but the visitor is not yet enrolled — used by manual-trigger tests before activation. |
variants | The experiment's variants, each with id, isControl, and its config. |
Manual-trigger and preview behaviour
Two query parameters change how enrolment works:
- Manual trigger. For a manual-trigger test the endpoint computes the visitor's bucket but does not enrol them until the runtime calls back with
activate=<experimentId>. That callback is whatisHypothesisActive()issues when the visitor reaches the point you decided marks the test. Until then the entry comes back withdeferred: true. - Preview. A
previewvalue forces one experiment to a chosen variant, skipping bucketing, and is validated against the current live experiments so a stale value is dropped. When a preview applies, the top-levelpreviewfield istrueso the runtime skips caching and tracking.
Failure handling
The endpoint is built to fail open — a broken app must never break the storefront:
- A missing shop or a
vidthat fails validation returns{ "assignments": [] }with status400. - An unknown or uninstalled shop returns an empty
assignmentsarray with status200, not an error. - If a single experiment throws while being resolved, it is skipped and logged; the remaining experiments still return their assignments.
Because the runtime handles all of this — the visitor id, the activate callback, preview, and applying each assignment — building against window.abtest and the JavaScript test workflow is the supported path. Read this page to understand what happens underneath.