Campaign context and language

The two headers that scope almost every request — x-campaign-id and Accept-Language.

x-campaign-id

A campaign is one retailer's commercial programme: its catalog slice, its pricing, its discount mechanism, its branding, its languages. Everything in the platform is scoped to one. Nothing is global.

Public routes therefore need to be told which campaign they are serving:

x-campaign-id: 3c1e5a90-7b2d-4f61-9a83-1d4e7f2b8c05

Send it on every catalog, checkout, voucher and analytics call. It is a UUID we issue you; it is not a secret, and it appears in your front-end code.

On authenticated routes the campaign comes from the token instead. The end-user JWT carries a campaign claim fixed at session creation, and /me/* routes read that. Where a route accepts both — the checkout routes, which serve guests and signed-in customers from the same handler — the JWT claim wins and the header is the fallback for guests. Sending a header that disagrees with the token does not switch campaigns; the token is authoritative.

A campaign that is not configured, or an id that does not exist, surfaces as 404 with urn:qup:error:campaign-not-found. On a checkout confirm this is the one 404 that is our configuration gap rather than your mistake — tell us if you see it.

Fetch the config once

GET /campaign/config returns the render-relevant settings for a campaign: which discount experience to show, what a wallet token is worth, the per-ticket surcharges, and the languages to offer. Fetch it at storefront boot and cache it. It changes rarely, and hardcoding any of it means a config change on our side silently desynchronises your UI.

Accept-Language

Language enters the API through exactly one door: the Accept-Language header, negotiated RFC 7231-style against the campaign's supported locales.

Accept-Language: nl-NL, en-GB;q=0.8

The platform picks the best available match and uses it for catalog translations, e-mail bodies, and the locale stamped on a session. Currently supported: nl-NL, en-GB, de-DE, fr-FR. Omitting the header falls back to the campaign's default.

There is no locale body field, query parameter or path segment, on any endpoint, and there will not be one. If you find yourself wanting to pass a language another way, pass the header.

Two consequences worth designing around:

  • Catalog responses vary by language. They are cached with Vary: X-Campaign-Id, Accept-Language. If you put a CDN or a shared cache in front of the API, key it on both headers or you will serve Dutch content to French customers.
  • A session remembers its language. The locale negotiated when the session was minted is stored on the JWT and returned as locale on the token response. Switch your UI to it after a session exchange, rather than assuming the browser's preference matched.

X-Correlation-Id

Optional on every request, and the single most useful thing you can send us.

X-Correlation-Id: 0f4c8e1a-9b2d-4c7e-8a1f-2d3b4c5e6f70

Send a UUID and it is echoed back in the response header, in meta.correlation_id on success, and as instance: urn:qup:correlation:<id> on every error. It follows the request through the entire platform — API, queues, workflows, the fulfillment provider call.

If you omit it we generate one, so it is always in the response. Log it against your own request records. When you report a problem, the correlation id is what lets us find the exact request in seconds instead of guessing from timestamps. It is also the only identifier we will ask you for, because it means we never need you to send us customer data.

Connecting to your own systems

Some campaigns keep the customer's loyalty balance, or their consent records, in the retailer's own
systems rather than in ours. Where that is the case, the platform reads from and writes to those
systems as part of the flows on this site — the balance a customer sees, the agreements they are
asked to accept.

How that connection is made is agreed per integration. There is no single mechanism to build
against: what the platform calls, what credential it presents, and which system is authoritative
all depend on what your side exposes. Bring us the systems you have and we will fit the
integration to them.

If your campaign keeps everything on our side, none of this applies and the endpoints behave as
their own pages describe.


Did this page help you?