Skip to main content

Astro Advanced

Geography headers

Where the location comes from

On server output, the c15t middleware reads the visitor's location from the request headers your host adds, and resolves the policy for that location. The headers never leave your server. With hosted(), the middleware sends the resolved values to your backend's /init as c15t override headers. With manifest(), it resolves the policy itself.

On a static or prerendered page, there is no request at build time. The browser's own /init request carries the location instead, and your backend or the /api/c15t/init route reads it from that request.

Which headers are read?

Within each group, the first header with a value wins:

InputHeaders, highest precedence firstSource
Countryx-c15t-country, cf-ipcountry, x-vercel-ip-country, x-amz-cf-ipcountry, x-country-code, x-countryc15t override, Cloudflare, Vercel, CloudFront, generic
Regionx-c15t-region, cf-region-code, x-vercel-ip-country-region, x-region-codec15t override, Cloudflare, Vercel, generic
Global Privacy Controlx-c15t-gpc, sec-gpcc15t override, browser signal
Languageaccept-languageBrowser

i18n.locale in the integration outranks accept-language. GPC values count only as 1 or 0. Any other value is treated as absent.

When no country header has a value, the location is unknown, and your policy's rule for an unknown location applies to every visitor. Policies explains rule matching.

Check that your host sends them

c15t does not keep a list of which hosts add which headers. Check your own deployment. Add a page that prints the inputs the middleware saw, deploy it, and open it.

src/pages/consent-debug.astro
---
export const prerender = false;
const { inputs } = Astro.locals.c15t;
---

<pre>{JSON.stringify(inputs, null, 2)}</pre>

country should hold your two-letter country code. If it is missing, your host does not add one of the headers above. Turn on the host's geolocation headers, or put a proxy such as Cloudflare in front of the site. Remove the page after the check.

The Node adapter behind a load balancer receives whatever headers the load balancer forwards. AWS CloudFront's own CloudFront-Viewer-Country header is not in the list. Map it to x-c15t-country at the edge that terminates traffic.

Only your edge may set x-c15t-* headers

x-c15t-country, x-c15t-region and x-c15t-gpc override the host's headers. A visitor who sends them chooses their own policy rule. The c15t middleware does not strip them. In production, the edge that terminates all traffic must delete incoming x-c15t-* headers before anything else sets them. Blocking direct access to your origin keeps requests on that edge, but it does not replace the stripping, because a forwarded client header still passes through.

The browser's own /init request carries the same overrides as the country, region and gpc query parameters, so that a cross-origin request needs no CORS preflight. The init route treats them like the x-c15t-* headers, so strip them from /init requests at the same edge.

Test a region locally

The override headers are how you test a policy without a VPN. Send them to a server-rendered page:

curl -H 'x-c15t-country: DE' http://localhost:4321/
curl -H 'x-c15t-country: US' -H 'x-c15t-region: CA' http://localhost:4321/
curl -H 'sec-gpc: 1' http://localhost:4321/
curl -H 'accept-language: de-DE,de;q=0.9' http://localhost:4321/

The HTML contains the banner for that region, or none for a region whose policy shows no banner. In a browser, a header-rewriting extension that sets x-c15t-country does the same for every request, including /init.

With hosted(), hosted({ headers: { 'x-c15t-country': 'DE' } }) pins the country for the server and the browser /init requests alike. Use it only in a development configuration.

Check the geography

  1. Deploy and open the site from two locations with different policy rules, for example through a VPN or your host's geo testing tools.
  2. Each location gets its own banner. An opt-in region shows the banner with optional categories denied, and a region with no banner rule shows none.
  3. Send x-c15t-country from outside your edge and confirm the edge removes it before it reaches the site.