Policy Packs
Policy packs are the backend source of truth for regional consent behavior. You pass them to c15tInstance({ policyPacks }), and c15t resolves a normalized runtime policy for each /init request based on the visitor's location.
Why Backend Packs
- Resolve policies from real request geo data (country/region) — no client-side guessing
- Centralize consent behavior for all clients (web, mobile, SDK)
- Issue signed
policySnapshotTokenvalues so later consent writes stay aligned with the original decision - Return explainability metadata (
policyDecision) for audit and debugging - Keep frontend packages thin by pushing legal/regional logic to one place
Quickstart
That gives you Europe opt-in, California opt-out, and silent everywhere else — with zero custom config.
Custom Pack
For full control, define your own policies:
Builder Helpers
The backend exports three helpers for assembling packs:
| Helper | Purpose |
|---|---|
policyBuilder.create() | Build a single PolicyConfig |
policyBuilder.createPack() | Build an ordered array of policies |
policyBuilder.createPackWithDefault() | Build a pack with a guaranteed default fallback |
The builder normalizes matcher input into the PolicyConfig shape and strips empty fields from the final payload. You can also use the raw PolicyConfig objects directly — the builder is a convenience, not a requirement.
Resolution Flow
GET /initarrives — c15t reads geo from request headers (country, region, jurisdiction)- Resolve policy — walks the pack in priority order: region → country → fallback (geo failure) → default
- Compute fingerprint — generates a stable SHA-256 hash of the resolved policy
- Sign snapshot — if
policySnapshot.signingKeyis configured, issues a JWT containing the decision - Return response — sends
policy,policyDecision, and optionalpolicySnapshotTokento the client - Consent write — when the user consents,
POST /subjectsvalidates the snapshot token and creates the consent record linked to the runtime policy decision
Matching Rules
Backend resolution order is fixed:
- Region — most specific (e.g., US-CA, CA-QC)
- Country — (e.g., US, DE, JP)
- Fallback — safety net when geo-location fails (no country detected). Only checked when
countryCodeisnull. - Default — catch-all for known locations that don't match any specific policy
Within the same matcher type, first match wins by array order. c15t also enforces:
- Unique policy IDs
- At most one default policy
- At most one fallback policy
iab.enabled: truewhen any policy usesmodel: 'iab'- No custom
ui.*overrides for IAB policies
Use inspectPolicies() to surface overlapping matchers and other warnings before deployment.
Validating Your Pack
inspectPolicies() checks your policy pack for errors and warnings before deployment:
Common errors caught:
- Multiple default policies
- Multiple fallback policies
- IAB policies without
iab.enabled: true - IAB policies with custom
ui.*overrides orpreselectedCategories - Duplicate or missing policy IDs
- Policies with no matchers and not marked as default or fallback
Common warnings:
- No default policy configured
- No fallback policy configured (geo-location failures will have no active policy)
- Overlapping country or region matchers across policies
Translation Profiles
Policies integrate with backend i18n profiles through i18n.messageProfile:
i18n.language can also force a concrete language for a policy when needed.
When i18n.messages is configured, c15t keeps language selection inside the
languages defined for the active messageProfile.
Built-in translations still provide the base strings for the selected language,
but they no longer introduce additional locales beyond the ones you configured.
This means messageProfile controls both:
- the policy-specific wording
- the allowed language set for that policy
defaultProfile is only used when a policy does not set messageProfile.
Each profile can optionally define its own fallbackLanguage for unsupported
browser locales. If omitted, c15t falls back to English when available,
otherwise to the first configured language in that profile.
For example:
For a policy with i18n: { messageProfile: 'eu' }, visitors can resolve to
en, fr, or de, but not to es, pt, or an unconfigured locale like
zh. If the browser asks for zh, c15t falls back to the eu profile's
fallbackLanguage, which would be en in this example.
Snapshot Tokens
When policySnapshot.signingKey is configured, /init returns a signed JWT alongside the resolved policy. The token contains the full policy decision metadata (fingerprint, matched geo, consent model) and is validated on POST /subjects.
Validation modes:
onValidationFailure: 'reject'— invalid, expired, or missing tokens return409 Conflict. This is the default and preserves the original/initdecision.onValidationFailure: 'resolve_current'— c15t falls back to resolving the current policy at write time. This favors availability over strict decision consistency.
Use reject when you want /subjects to preserve the original decision exactly. Use resolve_current only if your deployment prefers accepting writes even when the original snapshot cannot be verified.
Global Privacy Control (GPC)
Each policy can opt in to respecting the Global Privacy Control signal via consent.gpc:
When gpc: true and the visitor's browser sends a GPC signal (Sec-GPC: 1), marketing and measurement categories are automatically denied during auto-granting — honoring the user's opt-out preference.
When gpc is false or omitted, the GPC signal is ignored for that policy. This is the right default for GDPR policies where consent is already opt-in and GPC is redundant.
The californiaOptIn() and californiaOptOut() presets set gpc: true by default. The Europe presets omit it.
Scope Mode
Each policy can set consent.scopeMode to control how out-of-scope categories are handled:
strict— categories not listed in the policy'sconsent.categoriesare rejected. Use this for GDPR where only explicitly scoped categories should be collected.permissive— categories not listed in the policy are allowed through. Use this for regions with lighter requirements where you don't want to block unrecognized categories.
When omitted, scope mode defaults to permissive.
Fallback Matcher
The match.fallback flag provides a safety net when geo-location fails — when CDN headers are missing and countryCode is null. This is distinct from match.isDefault:
isDefault— catch-all for known locations that don't match any specific policy ("rest of world")fallback— safety net for unknown locations when geo fails ("assume strictest")
The fallback is only checked when countryCode is null. When geo works normally, resolution skips fallback entirely and uses the standard region → country → default flow.
The europeOptIn() and europeIab() presets include fallback: true by default, so EU-level consent applies automatically when geo headers are missing or when disableGeoLocation is enabled.
Info
At most one fallback policy is allowed. Use inspectPolicies() to verify — it warns when no fallback is configured and errors when multiple are defined.
Edge Cases
| Configuration | Result |
|---|---|
policyPacks omitted | Deprecated — legacy mode with no runtime policy in /init response. Will require policyPacks in 2.0 GA. |
policyPacks: [] | Explicit no-banner mode |
| Non-empty pack, no match, no default | Implicit no-banner mode |
Info
Omitting policyPacks entirely is deprecated and will be removed in 2.0 GA. Set policyPacks: [] explicitly if you want no-banner behavior.
For most production deployments, include both a default and a fallback policy so all traffic resolves deterministically.
Frontend Alignment
If you also use offline previews on the frontend:
- Keep the backend pack canonical — it resolves from real geo data
- Mirror it in frontend
offlinePolicy.policyPacksonly for local development, tests, or static demos - Validate the frontend preview against a real
/initresponse before shipping
Info
Frontend usage is documented in the React guide, Next.js guide, and JavaScript guide.