Privacy & compliance

Optional enrichment is off by default. When it is on, its browser pixel waits for Accept on the site's cookie banner, if the site shows one. Core analytics can start immediately or wait for consent depending on the site's compliance preset; identity capture, marketing fields, replay, fingerprinting, and behavioral trackers follow the consent and compliance settings you choose for that site.

Consent model

The SDK keeps a consent state in the _cs_consent local-storage slot, with three categories:

CategoryDefaultWhat it gates
analyticsgranted on opt-out presets (standard, ccpa); denied until consent is granted on opt-in presets (gdpr_strict, hipaa)pageview, click, scroll, session tracking; device fingerprint
marketingSame as analytics; always denied under Global Privacy Controlidentity capture: hashed email and phone, customer / account / social-login ids, captured form values
thirdPartySame as analytics; always denied under Global Privacy Control; revoking marketing revokes it tooclick ids read from ad-platform cookies; the optional enrichment pixel (see Cookie banners below)

Revoking marketing clears every identity slot from local storage, cookies and memory, and also revokes thirdParty unless the same call sets thirdParty itself. Revoking analytics stops tracking and discards unsent events. Every change emits a _consent_transition event so the server can scrub downstream state. A setConsent() call changes only the categories it names; the others keep their values and are not recorded as the visitor's answer.

Optional Visitor Enrichment

ClickStream can run an optional visitor enrichment check for a site. This is off by default.

When a site owner turns it on from the Sites page optional enrichment card or Settings → Enrichment, the SDK loads the DataShopper (DataMoon) browser pixel directly and passes the active ClickStream visitor ID as the match key. When the processor finds a match, ClickStream receives the match tied to that visitor ID and can complete the person profile.

Use this only when your privacy notice and consent flow allow optional identity enrichment. Before enabling it, site admins must confirm that they have reviewed the applicable privacy and sub-processor disclosures. When this setting is off, ClickStream does not run the optional enrichment check.

This setting is separate from normal analytics and from server-side enrichment after a form fill or explicit identify() call. Basic pageview, click, scroll, Signals, and first-party tracking do not require optional enrichment.

Cookie banners

Global Privacy Control, the region allow-list, page rules, the site's compliance profile and Only visitors who have consented still apply.

Enrichment is limited to the site's allowed regions, the US by default: the browser pixel loads only when the visitor's country is known and allowed, and server-side lookups skip a visitor whose known country is not allowed. An empty list allows every country, but never a visitor whose country is unknown.

The SDK reads the sources below. A refusal from any of them wins; otherwise the first source with an answer decides:

  1. Consent managers: IAB TCF v2 (purposes 1, 3 and 4), OneTrust (Targeting group C0004), Cookiebot, Osano, TrustArc, Didomi, Usercentrics, CookieYes, iubenda, Complianz, CookieHub, the Shopify Customer Privacy API and the WP Consent API, by their advertising or marketing category; a refusal in the HubSpot cookie banner. US opt-outs of sale, sharing or targeted advertising are refusals: the IAB US Privacy string (__uspapi or the usprivacy cookie), the IAB GPP US sections (__gpp), Osano's "Do Not Sell or Share", Shopify's sale of data and Didomi's US purposes. OneTrust, Cookiebot, TrustArc, iubenda and Complianz answers are also read from their cookies before their scripts load. A consent manager that has not loaded or does not answer (a blocked script, for example) holds the pixel for at most 10 seconds; one that says the visitor still has to answer holds it until they do.
  2. Your own signal: window.marketingConsent, or the thirdParty category set through window.cs.setConsent(), window.cs.acceptAllConsent() or window.cs.rejectAllConsent(). Only an explicit thirdParty: true grants. setConsent({ marketing: false }) refuses, and a call that leaves thirdParty out is no answer.
  3. Google Consent Mode v2: ad_storage (and ad_user_data, when set). A consent update to granted grants; an update to denied refuses. A denied default holds the pixel only while a consent manager or another consent update is detected, and for at most 10 seconds; with neither, it counts as no banner.
  4. Any other banner: a visible fixed, sticky or dialog element at least 200 px wide that is answered in place (a button, or a link that does not leave the page), holds no text, email or phone fields, and has "cookie" or "consent" in its own id, class or aria-label, or "cookie" in its text. A banner inside an open shadow root attached near the top of the page (as consent managers attach theirs) counts, and so does a consent manager's iframe in a fixed or dialog box. Accept, Agree, Allow, OK, Got it, I understand and Continue, and their Spanish, French, German, Italian, Portuguese and Dutch equivalents, count as Accept; Reject, Decline, "Accept only necessary", Settings and Manage do not. A click counts only when no other banner is showing. The answer is kept in local storage (_cs_banner_choice) for 13 months. A banner that closes without Accept or Decline is kept there as not accepted, and the pixel stays off on later pages until the visitor accepts.

A banner the SDK cannot find this way counts as no banner; use data-consent-banner="custom" below. A stored acceptance loads the pixel without the 2-second wait, but not while a banner is showing; neither does a Consent Mode grant or a consent manager's answer read from its cookie. A stored refusal keeps it off. Calling window.cs.setConsent({ thirdParty: false }), window.cs.setConsent({ marketing: false }) or window.cs.rejectAllConsent() later stops a loaded pixel and clears cookies with the site's configured prefixes.

Sites with their own banner

Add data-consent-banner="custom" (or consentBanner: 'custom' in window.ClickstreamConfig) and report the visitor's answer:

<script
  src="https://t.example.com/sdk/v2.js"
  data-key="cs_live_..."
  data-consent-banner="custom"
  async
></script>
// Accept
await window.cs?.acceptAllConsent();               // or: window.cs?.setConsent({ thirdParty: true })
// Decline
await window.cs?.setConsent({ thirdParty: false }); // or: window.cs?.rejectAllConsent()

The pixel then waits for that call (or window.marketingConsent = true, or a supported consent manager's answer) instead of loading after 2 seconds, and loads on the same page when you report Accept.

Only visitors who have consented

With this per-site option (Require enrichment consent in the enrichment settings), the pixel needs an explicit grant even when no banner is shown: an answer that allows advertising in one of the consent managers listed above, read from its script; window.cs.setConsent({ thirdParty: true }) or window.cs.acceptAllConsent(); or window.marketingConsent = true. A Consent Mode update, a click on a banner the SDK detected, a consent manager's cookie on its own, or a setConsent() call that leaves thirdParty out is not enough. Any refusal still wins.

Compliance presets

Set the preset at install time via data-compliance or the compliance config option:

<script
  src="https://t.example.com/sdk/v2.js"
  data-key="cs_live_..."
  data-compliance="gdpr_strict"
  async
></script>
installClickstreamPixel({
  apiKey,
  endpoint,
  compliance: 'gdpr_strict',
});
PresetConsent modeScrub levelIdentity / fingerprint defaultEncrypted-value retention
standardopt_outstandardallowed when visitor identifies90 days, auto-purge on
gdpr_strictopt_in — denied until consentaggressivedenied until identity consent180 days, auto-purge on
ccpaopt_outstandardallowed; honors DNT + GPC90 days, auto-purge on
hipaaopt_in — denied until consentaggressivedenied90 days, auto-purge on

Retention in the table above applies to encrypted raw values stored per site (retentionDays, configurable). A form submitted before its visitor record exists is held encrypted until it is attached to that record, then follows the same retention; one that is not attached within 21 days is deleted. Separately: the Analytics Engine event stream has ~90-day platform retention, R2 export objects are removed on a 90-day cleanup cycle, and the audit log is retained 7 years.

The preset sets server-side defaults via ClientConfig.complianceProfile, which the collector uses to reject any event that carries fields the preset would have stripped — defense-in-depth against an old SDK bundle being cached on a visitor's device.

Consent sources — CMP detection + setConsent()

The SDK does not render a consent banner. On opt-in presets (gdpr_strict, hipaa) tracking is denied until consent is granted, from one of two sources:

  1. Your CMP, auto-detected. The SDK detects OneTrust, Cookiebot, and Osano, mirrors their consent decision, and subscribes to consent changes. You don't have to double-integrate.
  2. An explicit setConsent() call. Any other CMP — or your own consent UI — can bridge its decision to the SDK:
window.cs?.getConsent();        // current consent state
await window.cs?.setConsent({ analytics: true, marketing: true, thirdParty: false });
await window.cs?.setConsent({ marketing: false });

With no CMP detected and no stored consent, opt-in presets collect nothing.

On every preset, the optional enrichment pixel follows the thirdParty category (a marketing refusal included) and the sources listed under Cookie banners above.

What's scrubbed at which level

standard scrub

aggressive scrub

All standard rules plus:

Raw PII — encrypted at rest, reveal-gated

When a site enables raw-value capture (email pre-encrypt on the server, form fills, IP addresses), values are AES-256-GCM encrypted with a unique key per site before they hit D1. Raw plaintext is never persisted anywhere.

Reveal requires all of:

  1. Dashboard operator with decrypt:read permission.
  2. Password re-authentication within the last 5 minutes (/decrypt re-auth gate).
  3. A recorded audit row (audit_log) that captures operator id + IP + target visitor + timestamp + reason text.
  4. Rate-limit allowance (10 reveals per operator per 15-minute window).

The audit log is append-only and retained 7 years; each site's Activity tab shows recent actions, and full history is available on request through support. Reveal access is controlled by the dashboard permission model, password re-authentication, and per-site encrypted storage; ClickStream operators do not bypass those gates.

Visitor rights — DSAR / export / deletion

Every dashboard site admin can:

Export and delete also reach a form submission that is still waiting for its visitor record, by its ClickStream ID or by the email address it contains. A submission whose email the collector could not read (end-to-end sealed, or stripped by the site's compliance profile) is found by ClickStream ID only.

When ClickStream archives identity records it no longer uses (cross-device match candidates that were never reviewed, and identity-processing failures too old to retry safely), it moves them out of the primary database into an encrypted archive in Cloudflare R2, keeps them there for 90 days from archiving regardless of the site's retention setting, and then deletes them. Visitor export and delete reach them there, as does deleting the site or the account.

Use tracker.setConsent({ marketing: false }) (clears identity from the browser) + the dashboard delete action (purges server-side records) for a full visitor scrub.

Server-side compliance enforcement

The SDK already strips fields based on the compliance profile before transmission, but the collector strips them again as defense-in-depth. A cached old SDK bundle on a visitor's device can't smuggle fields past the collector — the server's ClientConfig.complianceProfile rules run on every event regardless of SDK version.

Regional considerations

Data residency

DPA + sub-processor list

A Data Processing Agreement is available on request for ClickStream customers through legal@clickstream.com; dedicated data-residency terms can be appended to an MSA during provisioning. Current sub-processors:

The sub-processor list is published at einstein.clickstream.com/legal/sub-processors — we update that page before onboarding any new sub-processor.

See also