Consent gating
Consent is enforced server-side at the collector. The browser SDK
queues events while consent is 'unknown' and refuses to send while it is
'revoked', but a deliberately crafted client request that bypasses the
SDK will still be rejected by the collector — provided the operator
enables CONSENT_REQUIRED=true.
This page covers both layers.
Defaults
Section titled “Defaults”- The browser SDK defaults to
defaultConsent: 'unknown'. Events queue in memory; nothing leaves the page. - The collector defaults to
CONSENT_REQUIRED=falseso out-of-the-box development just works. Operators serving EU traffic should setCONSENT_REQUIRED=true.
Client-side states
Section titled “Client-side states”import { grantConsent, revokeConsent, getConsentState } from '@syntarie/tracking';
getConsentState(); // 'unknown' | 'granted' | 'revoked'| State | Behaviour |
|---|---|
'unknown' (default) | Events queue in memory. Nothing is sent. |
'granted' | Queued events drain. Subsequent sends go straight to the collector. |
'revoked' | The in-memory queue is purged. Subsequent sends are no-ops for the rest of the page. |
The 'unknown' → 'granted' transition happens when your consent UI calls
grantConsent(). The 'granted' → 'revoked' transition happens when
the user opts out (calls revokeConsent() from your settings UI).
There is no 'revoked' → 'granted' re-grant inside a single page
lifetime. After revoke, the user must reload and grant fresh.
The X-Consent header
Section titled “The X-Consent header”grantConsent(token?) accepts an optional opaque token. When provided,
the SDK attaches it as X-Consent: <token> on every subsequent collector
request:
grantConsent('eyJjaWQiOiIuLi4iLCJ0aW1lc3RhbXAiOi4uLn0=');The token shape is yours to define — it is opaque to the SDK and the collector. Common choices:
- A signed JSON object with
(consentId, timestamp, granted_categories). - A short opaque id you persist alongside the user’s audit trail.
The collector persists the value on every accepted event so an audit trail is reconstructable.
Server-side enforcement
Section titled “Server-side enforcement”Set CONSENT_REQUIRED=true on the collector. From that point:
- Requests without
X-Consentreturn403 consent_required. - The header value is persisted alongside the event (subject to the collector’s normal length cap).
The check is wired in front of every storage write, including ingest from the Node SDK and from webhook adapters. There is no path for an event to reach storage without the header when the env var is on.
DNT (Do Not Track)
Section titled “DNT (Do Not Track)”The browser SDK honours navigator.doNotTrack === '1' by default. A
DNT-blocked init never installs listeners and every subsequent send is a
no-op. The DNT check happens before consent — a DNT-on user does not
even reach the consent state machine.
To opt out of DNT respect (e.g. you are showing a UI that explicitly asks the user even when DNT is on):
init({ siteId, host, respectDnt: false });This is a privacy regression — gate it on a deliberate operator policy decision.
What about pageviews queued before consent?
Section titled “What about pageviews queued before consent?”The first pageview lands at init() time. If defaultConsent is
'unknown', that pageview is queued in memory along with everything else.
On grantConsent() it drains in order. No event leaves the page during
the 'unknown' phase.
If the user never grants consent, the queued events are dropped on
pagehide — they never reach storage.
What about already-stored anon IDs?
Section titled “What about already-stored anon IDs?”grantConsent does not retroactively un-anonymize stored events. The
collector hashes the IP per event regardless of consent state; there is no
“now you’ve consented, attach your real IP” path. This matches the GDPR
notion that data minimization at collection time is more robust than
retroactive scrubbing.
Audit trail
Section titled “Audit trail”Every consent-related collector decision (granted/revoked, accepted/ rejected) is captured at the structured-log level:
level=info reason=accepted_consent consent_token_len=64 site_id=site_marketinglevel=warn reason=consent_required site_id=site_marketingOperators can pipe these to their log aggregator and assert on rejection rates as part of their privacy compliance posture.