Skip to content

Mount /edgekit/ on your domain

Add rules to whatever serves your site (your CDN, load balancer or web server) so that requests to /edgekit/ on your domain reach your tenant host. Your pages then call EdgeKit at relative paths such as /edgekit/v1/decide.

  • Your tenant host: EdgeKit gives it to your organization. The samples use https://your-tenant-host.
  • Your site’s id: open the site from Delivery › Delivery › Sites. The id is the last part of the page address: site_example in /delivery/sites/site_example.
  • HTTPS on your site: EdgeKit’s sign-in cookies are Secure, and browsers do not store them for a page served over plain HTTP.
Request on your domain Forward to https://your-tenant-host as
/edgekit/v1/* The same path: /edgekit/v1/decide stays /edgekit/v1/decide
/edgekit/a/* The path without /edgekit: /edgekit/a/t becomes /a/t
/edgekit/i /i
/edgekit/com.snowplowanalytics.snowplow/tp2 /com.snowplowanalytics.snowplow/tp2

The first rule carries the client API and decide. The other three carry analytics events. The EdgeKit tracker posts to /edgekit/a/t by default (EdgeKitTrackerOptions); the last two rows are only for a tracker that sends to the standard tracker paths.

Leave every other path to your site. EdgeKit uses nothing outside /edgekit/ on your domain, so /v1/ on your own domain stays yours.

Forward to the tenant host:

  • the method, the body and the query string;
  • Cookie, which carries the user’s sign-in session;
  • Origin and the Sec-Fetch-* headers, which the origin check reads;
  • x-edgekit-site-id, which selects the site. The browser client sends it on every request;
  • Content-Type.

Set Host to the tenant host. Most forwarders do this when the target is a URL.

Return to the browser:

  • the status code, including redirects. Do not follow them in the forwarder: some EdgeKit paths answer 302 for the browser to follow;
  • every Set-Cookie header, each one separate;
  • Cache-Control and Vary. Answers that depend on who is asking are no-store. The public site configuration at /edgekit/v1/id/config may be cached for 5 minutes and varies on x-edgekit-site-id, so a cache in front of it must honour Vary.

The same rules as a fetch handler, for a server or edge runtime that takes one. It returns null for paths that are not EdgeKit’s, so your own handler serves them.

const TENANT_HOST = 'https://your-tenant-host';
// Analytics paths reach the tenant host without the /edgekit prefix.
const ANALYTICS_PREFIXES = [
'/edgekit/a/',
'/edgekit/com.snowplowanalytics.snowplow/',
];
function edgekitTarget(url: URL) {
const { pathname, search } = url;
if (pathname.startsWith('/edgekit/v1/')) {
return new URL(pathname + search, TENANT_HOST);
}
if (
pathname === '/edgekit/i' ||
ANALYTICS_PREFIXES.some((prefix) => pathname.startsWith(prefix))
) {
return new URL(pathname.slice('/edgekit'.length) + search, TENANT_HOST);
}
return null;
}
export async function forwardToEdgeKit(request: Request) {
const target = edgekitTarget(new URL(request.url));
if (!target) return null;
const headers = new Headers(request.headers);
// fetch sets Host from the target URL.
headers.delete('host');
return fetch(target, {
method: request.method,
headers,
body: ['GET', 'HEAD'].includes(request.method)
? undefined
: await request.arrayBuffer(),
// Hand redirects back to the browser.
redirect: 'manual',
});
}

Most /edgekit/v1/ paths have a /v1/ twin on the tenant host. They differ in which credential they take.

These answer exactly what their /v1/ path answers, with the same credentials:

Path What it is
/edgekit/v1/id/* Sign-in and the user’s own record. Not /upstream/*
/edgekit/v1/org/* The user organizations a user belongs to or administers
/edgekit/v1/grants The signed-in user’s grants
/edgekit/v1/newsletters/* Newsletter lists and where a user stands on each. Not /confirm or /unsubscribe

The browser credential, where /v1 takes an API key

Section titled “The browser credential, where /v1 takes an API key”
Browser path Server path
/edgekit/v1/decide /v1/decide
/edgekit/v1/gift-links /v1/gift-links
/edgekit/v1/recommendations /v1/recommendations

The browser path checks the site header and the origin, and never takes an API key. The server path takes a site-pinned API key, and a browser cannot hold one. On /edgekit/v1/decide, a request body that names a userId is refused with 403: in the browser, the user comes from the signed-in session only.

/edgekit/v1/checkout, the purchase. There is no /v1/checkout.

These have no /edgekit/v1/ path. Call them on the tenant host:

  • /v1/id/upstream/*: server calls with an API key.
  • /v1/newsletters/confirm and /v1/newsletters/unsubscribe: the confirmation and unsubscribe links in newsletter emails always point at the tenant host.
  • /v1/publications/{slug}/archive.json and /archive/*: a publication’s public archive.
  • /v1/webhooks/*: webhooks from your billing and messaging services.
  • /v1/content/sync and /v1/iglu/*: server calls with an API key.
  • /openapi.json, /docs, /v1/decide/openapi.json and /health.

The origin check compares each browser request’s Origin with the site’s allowed origins. To set them:

  1. Open the site from Delivery › Delivery › Sites.
  2. In Settings, under Origins, enter every origin your pages are served from in Allowed, one per line: scheme, host and port exactly, such as https://www.example.com.
  3. Save, then stage and deploy the draft on Delivery › Deployments › Versions. Requests read the deployed site, so the new list applies from that deploy.

The rules the check follows:

  • Your own site’s origin belongs in the list. Browsers send Origin on a POST, even a same-origin one.
  • Exact match only: https://example.com and https://www.example.com are two origins, and so are http:// and https://. An entry with a path or a trailing slash is refused when you save.
  • No wildcards: an entry containing * never matches anything.
  • At most 50 entries per site.
  • An empty list refuses every browser request that carries an Origin. Delivery › Delivery › Sites marks such a site denies all.
  • A request with no Origin passes only when the browser’s Sec-Fetch-Site header says it is same-origin, or when it carries no fetch metadata at all, as a server or app request does.

A refused request answers 403 forbidden with no CORS headers. See Troubleshooting.