Identity and Consent
Episode 2: the token is born in the browser, persisted for 400 days, and consent gates only what the SDK sends, never what it decides or renders
Copy this guide and your current settings as a prompt for your AI agent.
Preview agent promptSee the guide, code and settings included when you copy.
Every hash in episode 1 started from a token. This episode is where that token comes from, and what happens when a visitor has not said yes to cookies yet. Short version: the page still decides, still renders, and sends not one byte until they do.
On this page
01Where identity resolves
Identity is the third station of boot: resolve the config, resolve the
project, then resolve identity (sdk-js/src/init/resolveIdentity.ts). It
runs before any listener attaches and before any DOM change, because
everything downstream stamps that token onto what it records.
02Token birth
By default the token is a cookie, coframe.user.token. Read it; if it is
there, that is the visitor. If not, mint one with crypto.randomUUID(), or a
version-4 fallback built from getRandomValues. A brand-new token is a
brand-new input to every hash from episode 1.
03Bring your own token
Set waitForUserToken: true in the config and push { setUserToken: 'your-id' }
onto CFQ. The SDK waits up to five seconds; if nothing arrives it exits
gracefully: no variants, no events. When you bring the token, the SDK never
writes its own cookies.
04Persisting the cookie
| Attribute | Value |
|---|---|
| Lifetime | 400 days, Chrome’s maximum (COOKIE_DAYS) |
| Path | / |
| Flags | secure |
| Domain | A ladder: for sub.tui.co.uk it tries .tui.co.uk, then .co.uk, then .uk; browsers reject the public suffixes and the widest valid one sticks, so subdomains share the token |
The SDK reads the cookie back; a mismatch logs failed_to_set_cookie.
Alongside it, a visit counter (coframe.cvc, coframe.lastVisitTime) counts
a new visit only if two hours have passed.
05Consent modes
Without configuration, cookies are enabled at boot. Set
waitForCookiesEnabled: true and boot starts with cookies off. Your banner
pushes one command: window.CFQ.push({ cookiesEnabled: true }). Pushed before
boot, the SDK replays the last one it finds (findLastCmd). Pushed later, a
subscription catches it (subscribeToCmd). Order never matters.
06What consent does not gate
The line from sdk-js/src/event-flags.ts: consent only gates sending.
Assignment still runs. Variants still render. The session, the page-view and
the clicks all still queue, into three in-memory arrays. Every flush trigger,
the 32-event size trigger, the three-second timer and page-hide, hits the
same guard (shouldSendEvents) and returns. Nothing leaves.
07When consent lands
applyCookieConsent fires only on a real transition. Cookies switch on; the
token and visit cookies are written; and the listener in setupTracking.ts
flushes all three queues through sendBeacon (sessions, events, monitoring)
and broadcasts the consent-gated treatment callbacks exactly once. Push
false later and sending stops again. Nothing is deleted.
08One visitor, one timeline
- Boot: token minted in memory, variant rendered, events queued, network silent.
- 12 s in, they accept: cookie written, queues drained, callback fired.
- Next visit: cookie found, same token, same bins, same seats.
- A visitor who never accepts: a new token every page, nothing persisted, nothing sent.
09Where in the code
| File | What to look for |
|---|---|
sdk/sdk-js/src/index.ts |
Init order: resolveConfig → resolveProject → resolveIdentity → setupTracking → modifyPage |
sdk/sdk-js/src/init/resolveIdentity.ts |
waitForUserToken, getOrGenerateUserToken, persistCookies, applyCookieConsent |
sdk/shared/cookies.ts |
Cookie names, COOKIE_DAYS, generateUUID, the domain ladder, the read-back check, trackVisitCookie |
sdk/sdk-js/src/event-flags.ts |
canSendWithConsent, shouldSendEvents |
sdk/sdk-js/src/api.ts |
The three queues and flushQueuedEvents |
sdk/sdk-js/src/init/setupTracking.ts |
onConsentChange, consent-gated callbacks |
# Identity and Consent
Episode 2: the token is born in the browser, persisted for 400 days, and consent gates only what the SDK sends, never what it decides or renders
Every hash in [episode 1](../docs/bucketing.html) started from a token.
This episode is where that token comes from, and what happens when a visitor
has not said yes to cookies yet. Short version: the page still decides, still
renders, and sends not one byte until they do.
[Video: Video: Runtime Walkthrough, episode 2: token birth, bring your own token, the cookie, consent modes, what consent does not gate, and when consent lands (3m 21s, narrated)](assets/videos/sdk-internals-02-identity-consent.mp4)
Watch: 3m 21s — Runtime Walkthrough, episode 2: token birth, bring your own token, the cookie, consent modes, what consent does not gate, and when consent lands
## Where identity resolves
Identity is the third station of boot: resolve the config, resolve the
project, then resolve identity (`sdk-js/src/init/resolveIdentity.ts`). It
runs before any listener attaches and before any DOM change, because
everything downstream stamps that token onto what it records.
## Token birth
By default the token is a cookie, `coframe.user.token`. Read it; if it is
there, that is the visitor. If not, mint one with `crypto.randomUUID()`, or a
version-4 fallback built from `getRandomValues`. A brand-new token is a
brand-new input to every hash from episode 1.
## Bring your own token
Set `waitForUserToken: true` in the config and push `{ setUserToken: 'your-id' }`
onto `CFQ`. The SDK waits up to five seconds; if nothing arrives it exits
gracefully: no variants, no events. When you bring the token, the SDK never
writes its own cookies.
## Persisting the cookie
Attribute |
Value |
Lifetime |
400 days, Chrome’s maximum (`COOKIE_DAYS`) |
Path |
`/` |
Flags |
`secure` |
Domain |
A ladder: for `sub.tui.co.uk` it tries `.tui.co.uk`, then `.co.uk`, then `.uk`; browsers reject the public suffixes and the widest valid one sticks, so subdomains share the token |
The SDK reads the cookie back; a mismatch logs `failed_to_set_cookie`.
Alongside it, a visit counter (`coframe.cvc`, `coframe.lastVisitTime`) counts
a new visit only if two hours have passed.
## Consent modes
Without configuration, cookies are enabled at boot. Set
`waitForCookiesEnabled: true` and boot starts with cookies off. Your banner
pushes one command: `window.CFQ.push({ cookiesEnabled: true })`. Pushed before
boot, the SDK replays the last one it finds (`findLastCmd`). Pushed later, a
subscription catches it (`subscribeToCmd`). Order never matters.
## What consent does not gate
The line from `sdk-js/src/event-flags.ts`: consent only gates sending.
Assignment still runs. Variants still render. The session, the page-view and
the clicks all still queue, into three in-memory arrays. Every flush trigger,
the 32-event size trigger, the three-second timer and page-hide, hits the
same guard (`shouldSendEvents`) and returns. Nothing leaves.
## When consent lands
`applyCookieConsent` fires only on a real transition. Cookies switch on; the
token and visit cookies are written; and the listener in `setupTracking.ts`
flushes all three queues through `sendBeacon` (sessions, events, monitoring)
and broadcasts the consent-gated treatment callbacks exactly once. Push
`false` later and sending stops again. Nothing is deleted.
## One visitor, one timeline
- Boot: token minted in memory, variant rendered, events queued, network
silent.
- 12 s in, they accept: cookie written, queues drained, callback fired.
- Next visit: cookie found, same token, same bins, same seats.
- A visitor who never accepts: a new token every page, nothing persisted,
nothing sent.
## Where in the code
File |
What to look for |
`sdk/sdk-js/src/index.ts` |
Init order: resolveConfig → resolveProject → resolveIdentity → setupTracking → modifyPage |
`sdk/sdk-js/src/init/resolveIdentity.ts` |
`waitForUserToken`, `getOrGenerateUserToken`, `persistCookies`, `applyCookieConsent` |
`sdk/shared/cookies.ts` |
Cookie names, `COOKIE_DAYS`, `generateUUID`, the domain ladder, the read-back check, `trackVisitCookie` |
`sdk/sdk-js/src/event-flags.ts` |
`canSendWithConsent`, `shouldSendEvents` |
`sdk/sdk-js/src/api.ts` |
The three queues and `flushQueuedEvents` |
`sdk/sdk-js/src/init/setupTracking.ts` |
`onConsentChange`, consent-gated callbacks |