Consent Gating
Opt-in, off by default for every company. When off, the loader behaves exactly as it always has (identification/enrichment, live view, and rrweb recording initialize immediately). When on, those features wait for an affirmative per-category consent signal. Chat is never gated.
Implemented in the loader (backend/public/widget.js, the ConsentGate
object), configured per company via companies.consent_gating, edited in
Settings → Widget → Install (frontend/src/views/settings/widget/tabs/InstallTab.vue).
Schema — companies.consent_gating (strict subdoc on CompanyModel)
| Field | Default | Meaning |
|---|---|---|
enabled | false | Master switch |
block_until_decision | true | true: hold session creation + widget render until the visitor answers the banner. false: load immediately, default all-accepted, banner still shows |
categories.identification | true | Ask for the Identification category |
categories.behaviour | true | Ask for the Behaviour category |
categories.calls_recordings | false | Reserved — accepted and persisted by the endpoint, not yet enforced anywhere |
texts.* | built-in copy | title, banner_body, prefs_body, identification_label/_desc, behaviour_label/_desc — empty string falls back to loader defaults, max 1000 chars each |
colors.* | accent #111111, background #ffffff, text #111111 | Banner/modal theming; loader accepts valid hex only (bad values fall back — no CSS injection) |
Only categories the company asks for gate their feature or appear in the
Preferences modal (ask.<cat> = enabled && categories.<cat> in
ConsentGate.configure()).
What each category gates
| Category | Gates |
|---|---|
identification | Snitcher/Apollo visitor identification — enforced server-side: session create sends tracking_consent: ConsentGate.identificationAllowed(); UserSessionController skips Snitcher (and therefore Apollo) when the company gates identification and consent isn't true |
behaviour | Session recording, live and replay — the loader only loads rrweb/live_session.js/recording scripts after Behaviour consent (ConsentGate.loadRecording()), so ungranted sessions capture nothing |
calls_recordings | Reserved, built later |
| (chat) | Never gated — window.knockKnockConsent.chat is always true |
A late grant is forward-only: ConsentGate.onConsentUpdated() calls
POST /user-session/:session_id/grant-tracking (mid-session Snitcher/Apollo
opt-in) and starts recording from that point; nothing is backfilled.
Endpoint
PUT /company/:_id/consent-gating — CompanyRoutes.js →
CompanyController.updateConsentGating (JWT-guarded like the other company
routes). Accepts partial { enabled, block_until_decision, categories, texts, colors }; writes dot-paths only (never clobbers the subdoc); rejects non-hex
colors with 400.
Host-page contract (window.kkSetConsent)
Defined at the very top of the loader so a site's consent tool can call it as soon as the loader is present:
// boolean → applies to identification + behaviour
window.kkSetConsent(true)
// or partial object
window.kkSetConsent({ identification: true, behaviour: false })
- State lives on
window.knockKnockConsent = { chat: true, identification, behaviour }. The loader initializes it withwindow.knockKnockConsent || {...}— a host page may pre-seed the object before the loader loads (pre-init safe path);kkSetConsentitself only exists once the loader script has run. - Every call dispatches a
kk-consent-updatedevent; the loader listens and applies mid-session changes (grant-tracking + recording load). - Visitor decisions from the bundled banner persist in localStorage under
kk_consent(ConsentGate.STORAGE_KEY); returning visitors get their saved choice re-applied without a banner.
Banner flow (bundled fallback UI)
When enabled and no stored choice: banner (#kk-consent-banner, Accept All +
Preferences) → optional preferences modal (#kk-consent-modal, per-category
toggles; a non-asked category renders as a locked row). block_until_decision
holds ConsentGate.awaitDecision() — session creation and widget render — until
markDecided(). Non-blocking mode defaults both categories to accepted and
still shows the banner so the visitor can change their mind. Sites with their
own CMP drive kkSetConsent instead — any call fires kk-consent-updated,
which marks the decision made and (once the session exists) replays it as a
mid-session change.
Related
- Installation — the loader this lives in
- Lead Intelligence — what Identification consent gates
- Session Recording — what Behaviour consent gates