Consent — Consent Mode v2 banner config
The store's CMP configuration for the built-in Consent Mode v2 banner (consent-management card). Defaults are always applied server-side, so the payload is complete and renderable even for an unconfigured store (compliant built-in modal, BG + EN copy shipped).
Storefront wiring (the trap that matters)
- Mount
<ConsentInit>as the first child of<body>— it sets the synchronous Consent Mode v2 DEFAULT and must never wait on this fetch (async default = first-hit consent race). Resolve this config server-side (RSC) and inline it into the document. - Render the built-in banner only when
enabled && mode === "builtin". mode: "external"= the merchant's CMP owns the UI and must write the same_1c_consentcookie (or callsetConsent()) — all Cartbase-side tag gating works off that one seam.- Choices persist 12 months in the cookie. Rybbit (platform analytics) stays outside consent by design.
- Pair with integrations.md:
tracking.consent_requiredmirrorsenabledhere — tags mount only through the consent gate when true.
GET /api/store/consent — the CMP config
- Purpose: everything the banner needs to render, per locale.
- Auth: anon (
x-client-id). - Request: no params.
- Response — the EXACT allowlist (nothing else will ever appear here):
{
"consent": {
"enabled": true,
"mode": "builtin", // "builtin" | "external"
"layout": "modal", // "modal" (blocking) | "banner-bottom" (non-blocking)
"privacy_href": "/cookies",
"reject_on_first_layer": false,
"copy": {
"bg": {
"title": "Преди да продължиш",
"body": "…",
"privacy_link_label": "…",
"accept_label": "…", "settings_label": "…", "reject_label": "…",
"settings_title": "…",
"accept_all_label": "…", "save_label": "…", "reject_all_label": "…",
"necessary_label": "…", "necessary_description": "…",
"analytics_label": "…", "analytics_description": "…",
"ads_label": "…", "ads_description": "…"
},
"en": { /* same 16 keys — every field always present per locale */ }
}
}
}- Errors:
400 missing_client_idonly — the payload itself always succeeds (a corrupt/missing stored config degrades to defaults, never to a broken banner). - SDK:
consent.getConsent(client) - Components:
<ConsentInit>+<ConsentBanner copy={copy[locale]} layout privacyHref rejectOnFirstLayer>(consent family; reference implsrc/components/storefront/consent/). - Settings: admin → Settings → Consent (enabled/mode/layout/ privacy_href/copy per locale).
# Complete, renderable config — even on an unconfigured store the defaults
# make every documented key present.
BODY=$(curl -sf "$BASE/api/store/consent" -H "x-client-id: $CLIENT_ID")
echo "$BODY" | grep -q '"consent"'
echo "$BODY" | grep -q '"mode"'
echo "$BODY" | grep -q '"layout"'
echo "$BODY" | grep -q '"privacy_href"'
echo "$BODY" | grep -q '"reject_on_first_layer"'
echo "$BODY" | grep -q '"analytics_label"'
echo "$BODY" | grep -q '"ads_description"'
# Missing tenant header → the standard 400.
STATUS=$(curl -s -o /dev/null -w '%{http_code}' "$BASE/api/store/consent")
test "$STATUS" = 400