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:
| Category | Default | What it gates |
|---|---|---|
analytics | granted 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 |
marketing | Same as analytics; always denied under Global Privacy Control | identity capture: hashed email and phone, customer / account / social-login ids, captured form values |
thirdParty | Same as analytics; always denied under Global Privacy Control; revoking marketing revokes it too | click 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
- When no consent banner is detected, enrichment loads: ClickStream treats the site's visitors as consenting, and the pixel loads about 2 seconds after the page finishes loading. If a banner appears later on that page, the pixel stops, and cookies with the site's configured prefixes are cleared, until the visitor answers it.
- When the SDK detects a banner (see below), the pixel waits until the visitor clicks Accept. If the visitor declines, it does not load.
- A refusal from any source keeps the pixel off, whatever another source says, and stops it if it already loaded.
- Deciding whether your site needs a banner, and providing one, is your responsibility.
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:
- 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 (__uspapior theusprivacycookie), 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. - Your own signal:
window.marketingConsent, or thethirdPartycategory set throughwindow.cs.setConsent(),window.cs.acceptAllConsent()orwindow.cs.rejectAllConsent(). Only an explicitthirdParty: truegrants.setConsent({ marketing: false })refuses, and a call that leavesthirdPartyout is no answer. - Google Consent Mode v2:
ad_storage(andad_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. - 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',
});
| Preset | Consent mode | Scrub level | Identity / fingerprint default | Encrypted-value retention |
|---|---|---|---|---|
standard | opt_out | standard | allowed when visitor identifies | 90 days, auto-purge on |
gdpr_strict | opt_in — denied until consent | aggressive | denied until identity consent | 180 days, auto-purge on |
ccpa | opt_out | standard | allowed; honors DNT + GPC | 90 days, auto-purge on |
hipaa | opt_in — denied until consent | aggressive | denied | 90 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:
- 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.
- 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
- Query strings on
page.urlmatching common PII patterns (token=,email=,password=,access_token=,ssn=) are dropped. - Click element text longer than 128 characters is truncated.
- Form field values are NOT captured unless Universal Form-Fill Capture is explicitly enabled per site.
- Even with form capture on, payment-card, CVV, bank, Social Security and other government-ID fields are never captured, at any scrub level. Values are dropped from every field, in the browser and again on the server, when they look like a card number or hold 12 or more digits, contain a Social Security number in its usual format (123-45-6789), or give a tax or government ID number with its label.
aggressive scrub
All standard rules plus:
document.referreris reduced to origin + path (query + fragment stripped).- Any field matching the SDK's broad PII heuristics (name, DOB, SSN, license, CC, health terms) is skipped even under form capture.
- Click text is truncated to 64 characters.
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:
- Dashboard operator with
decrypt:readpermission. - Password re-authentication within the last 5 minutes (
/decryptre-auth gate). - A recorded audit row (
audit_log) that captures operator id + IP + target visitor + timestamp + reason text. - 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 all data for a visitor — CSV + JSON bundle of every event, identity signal, form submission, and encrypted-field reveal associated with the
_cs_uidor any of its linked identifiers. - Delete a visitor — deletes the person record from primary stores (dashboard D1, identity graph, enrichment cache) immediately and propagates erasure to the collector; durable tombstones suppress the visitor from all dashboard reads and every future exact export. Completed self-serve export artifacts are revoked and deleted wholesale. Finite-retention canonical ledger hours are physically purged on schedule; keep-all sites retain the suppression record so historical data cannot make the visitor reappear.
- Honor DNT / GPC — the SDK auto-respects
navigator.doNotTrack === '1'and Global Privacy Control (navigator.globalPrivacyControl === true) when compliance preset isccpa. CCPA already covers California's CPRA-specific GPC requirements; no separate preset is needed.
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
- GDPR / UK GDPR — use
gdpr_strictfor EU-oriented defaults. Tracking is denied until consent is granted via your CMP or an explicitsetConsent()call. Processing under "legitimate interest" is deliberately NOT supported for marketing / identity categories. - CCPA / CPRA (California) — use
ccpa. The preset is opt-out — no consent gate on load; GPC and DNT signals are honored. Many CCPA sites pair it with a "Your Privacy Choices" link in their footer — your counsel decides what notice your site needs. CPRA's GPC enforcement is covered by the same preset — there is no separatecprapreset to configure. - HIPAA (US healthcare) — use
hipaafor HIPAA-aware settings: opt-in consent (denied until granted), aggressive scrubbing, and optional enrichment hard-blocked. Not for PHI without a signed BAA — contact support before processing PHI. - PIPEDA (Canada) —
standard+ explicit consent flow on first visit meets PIPEDA baseline. - LGPD (Brazil) — use
gdpr_strict— LGPD's consent requirements are essentially a GDPR subset.
Data residency
- Primary storage — Cloudflare's global network (Analytics Engine + D1 + KV) and PlanetScale managed PostgreSQL in AWS us-east-1 (United States), reached only through Cloudflare Hyperdrive. Logical region assignment for Cloudflare storage follows Cloudflare's data-at-rest residency program.
- EU-only residency — available only under a dedicated Enterprise deployment with a signed DPA and region-scoped Cloudflare and database configuration.
- US-only residency — available only under the same dedicated-deployment model.
- Cross-border transfers — identity enrichment is governed by the site's consent and compliance profile. Raw email and phone values are encrypted before storage, enrichment is disabled when the site configuration blocks it, and vendor-specific identity details are not exposed in customer-facing product surfaces.
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:
Cloudflare — Hosting, edge compute, DNS, analytics storage, and security services.
Data involved: Visitor events, account configuration, and operational logs. Cloudflare operates the edge that every ClickStream request terminates on: the visitor’s browser connects to it to download the SDK and to send each event, so it receives the visitor’s IP address, user agent and request headers on every one of those requests.
Hosts:
cloudflare.comPlanetScale — Managed PostgreSQL hosting for ClickStream’s databases.
Data involved: Account configuration and the visitor, identity and event records the platform stores, in the same form. Only ClickStream’s servers connect to it, through Cloudflare Hyperdrive; visitors’ browsers never do.
Hosts:
planetscale.com(managed PostgreSQL in AWS us-east-1, United States)Stripe — Billing, subscriptions, invoices, and payment processing.
Data involved: Account and billing details. Stripe does not receive ClickStream event data.
Hosts:
stripe.comHubSpot — ClickStream’s own sales and account management for ClickStream customers.
Data involved: ClickStream customer account details: the account holder’s name, login email, and phone number and company name when provided, signup date and email verification state, package, billing and subscription status, whether the account is a lead or a paying, complimentary or former customer, Stripe invoice, payment and refund history, and the names and addresses of the properties the account tracks. HubSpot does not receive ClickStream event data or any tracked visitor’s data.
Hosts:
hubspot.com, hubapi.comResend — Delivery of the emails ClickStream sends: account verification, password resets, invitations, notifications to account holders, and ClickStream’s own operator alerts.
Data involved: The names and email addresses of ClickStream account holders, the email addresses of people they invite to a property, and the full content of each email ClickStream sends: verification and password-reset links, invitations naming the inviter and the shared properties, notifications from automations an account holder sets up, and notices about data subject requests for their properties, which never identify the data subject. Resend also carries ClickStream’s internal operator alerts, which contain operational details such as property ids and tracking hostnames, and ClickStream’s internal new-account notifications, which contain the new account holder’s name and email address, and their phone number and company name when provided. Resend does not receive ClickStream event data or any tracked visitor’s data.
Hosts:
resend.comNango — Integration platform for the third-party tools a customer connects to ClickStream, such as its CRM. Planned: not yet in use.
Data involved: Once in use: the access credentials a customer grants for the tools it connects, and the records the customer chooses to send to those tools.
Hosts:
nango.devStirista — Demand-side platform for the advertising audiences a customer chooses to activate. Planned: not yet in use.
Data involved: Once in use: the audience segments a customer chooses to activate for advertising, sent as hashed identifiers such as hashed email addresses, and the campaign settings that go with them.
Hosts:
stirista.comDataShopper (DataMoon) — Optional person-profile enrichment when a site owner enables enrichment.
Data involved: Two separate transfers, and the one in the browser is the larger. FROM CLICKSTREAM’S SERVERS: a hashed email identifier (MD5) and a ClickStream visitor match key. IN THE VISITOR’S BROWSER: ClickStream loads this vendor’s own script directly into the page, so the visitor’s browser connects to the vendor’s hosts itself and discloses the visitor’s IP address on the script request and on every request the script then makes; those requests carry at least the page’s origin in their Referer header. Once running, the script has whatever access to the page any script loaded by that page has — the full page URL and referrer, the page’s content, browser and device properties, and cookies it sets and reads, including cookies written into your own site’s cookie store. ClickStream additionally exposes the ClickStream visitor id to it as window.customUserId, and the script loads the LiveIntent dependency listed below. What it receives in the browser is therefore bounded by what runs in the page, not by what ClickStream sends. All of this applies only to sites with optional enrichment enabled and the required consent posture; disabling enrichment stops both legs. A hashed email address is personal data and is not made anonymous by hashing.
Hosts:
app.datamoon.com, datashopper.comLiveIntent (engaged by DataShopper/DataMoon) — Browser-side identity resolution supporting optional enrichment. Runs only on sites where enrichment is enabled and the required consent is present.
Data involved: A SECOND SCRIPT EXECUTING IN THE PAGE, not a beacon. The enrichment vendor’s script loads this vendor’s own script into the same page (currently d-code.liadm.com), and it may open identity-sync IFRAMES to these hosts — which is why the CSP guidance for optional enrichment includes frame-src and child-src for them and not only script-src. The visitor’s browser therefore connects to these hosts itself and discloses the visitor’s IP address on the script request, on every request the script then makes, and on every frame it opens; those requests carry at least the page’s origin in their Referer header. Once running, the script has whatever access to the page any script loaded by that page has — the full page URL and referrer, the page’s content, browser and device properties, and cookies it sets and reads. What it receives in the browser is therefore bounded by what runs in the page, not by what ClickStream sends: ClickStream transmits nothing to this recipient from its own servers. LiveIntent resolves the visitor against its own identity graph keyed on the IP address, deriving the hashed email used as the enrichment match key. Disabling optional enrichment stops this processing entirely.
Hosts:
liadm.com, liveintent.com(including subdomains such as d-code, idx, rp)
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
- Security — encryption primitives + per-tenant isolation
- Optional enrichment — what gets hashed where
- Event schema — consent transitions —
_consent_transitionevent - First-party tracking (recommended) — tenant-scoped DNS + SSL provisioning