Skip to main content

Authentication

Every call has to answer two questions: which shop, and which customer. You never send an API key — Shopify answers both for you.

There are two supported ways in.

1. Shopify App Proxy — use this from a theme​

Call the loyalty app on the shop's own domain:

https://your-shop.myshopify.com/apps/loyalty-proxy/member

From a theme you write it as a relative URL, so it works on the live domain, on preview themes and on development stores alike:

const res = await fetch('/apps/loyalty-proxy/member');

Shopify forwards the call to us, signs it, and appends the id of the customer whose session cookie the browser sent. There is nothing for you to add — no header, no token, no customer id. A request that is not signed by Shopify is rejected.

This is why the proxy path only works from a page served on the shop domain: the customer session lives there.

2. Session token — use this from a Shopify UI extension​

Checkout and customer-account UI extensions run in a sandboxed frame and cannot use the App Proxy. They call the API host directly and identify the customer with a Shopify session token:

import {api} from '@shopify/ui-extensions/checkout';

const token = await api.sessionToken.get();

const res = await fetch('https://api.ecommerce.knistrloyalty.cloud/storefront/member', {
headers: {'X-Shopify-Session-Token': token},
});

Note the header name: X-Shopify-Session-Token, not Authorization: Bearer. The customer is read from the token itself, so a logged_in_customer_id in the query string is ignored on this path.

Tokens are short-lived — fetch a fresh one per call rather than caching it.

Headless storefronts on your own domain​

The customer's Shopify session only exists on the shop domain, and Shopify only issues session tokens inside its own extension surfaces. A storefront served from your own domain therefore has neither, and cannot call the Storefront API directly today.

If that is your setup, talk to us before you build — it is on the roadmap and we would rather design it with you than have you work around it.

Responses you must handle​

StatusMeansDo this
401No customer is logged inSend the visitor to /account/login
404Logged in, but not a loyalty member yetShow your "Join the programme" call to action
409The action was already done (e.g. birthday already set)Show the existing value, not an error
400The request itself is wrong — a missing optionId, a birthdate that is not --MM-DD, or consentGiven not trueFix the call; this one is on you, not on the customer
503Shopify did not answer when we tried to read the customerAsk the customer to try again in a moment

401 and 404 are normal states of a loyalty UI, not failures — a signed-out visitor and a not-yet-joined customer are the two most common visitors you will have.

400 and 503 are the two you are most likely to forget. POST /register can return either, so a join button that assumes success will silently do nothing when one of them comes back. Every error body is the same shape: {"error": "..."}, in English, meant for your logs rather than for the customer.

One endpoint is different​

GET /config returns the shop's programme configuration — branding, earning rules, reward catalogue. It is shop-scoped: it needs no logged-in customer and never returns customer data. You can call it to render the rewards page for signed-out visitors.