From ceeded5f4d4e4a2eddcafbb0b98b19754584f3c9 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 10 Apr 2026 15:59:34 +1000 Subject: [PATCH 1/8] docs(pixel): add integration guide, CSP docs, and CDN validation script Provide a complete README for the tracking pixel covering the Game Page snippet, consent modes, auto-tracked events, cookie reference, CSP directives, and a browser compatibility validation checklist. Also adds a shell script to verify the CDN bundle is deployed and within the 10KB gzip budget. Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 178 ++++++++++++++++++ .../audience/pixel/scripts/validate-cdn.sh | 101 ++++++++++ 2 files changed, 279 insertions(+) create mode 100644 packages/audience/pixel/README.md create mode 100755 packages/audience/pixel/scripts/validate-cdn.sh diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md new file mode 100644 index 0000000000..dc971b5799 --- /dev/null +++ b/packages/audience/pixel/README.md @@ -0,0 +1,178 @@ +# @imtbl/pixel — Immutable Tracking Pixel + +A drop-in JavaScript snippet that captures device signals, page views, and attribution data for Immutable's events pipeline. Zero configuration beyond a publishable key. + +## Quick Start + +Paste this snippet into your site's `` tag: + +```html + +``` + +Replace `YOUR_PUBLISHABLE_KEY` with your project's publishable key. + +The script loads asynchronously and does not block page rendering. The default consent level is `none` — the pixel loads but does not collect until consent is explicitly set (see [Consent Modes](#consent-modes)). To start collecting anonymous device signals immediately, add `"consent":"anonymous"` to the init object. + +## Consent Modes + +The `consent` option controls what the pixel collects. **Default is `none`** (no events fire until consent is set). + +| Level | What's collected | Cookies set | Use case | +|-------|-----------------|-------------|----------| +| `none` | Nothing — pixel loads but is inert | None | Before consent banner interaction | +| `anonymous` | Device signals, attribution, page views, form submissions, link clicks (no PII) | `imtbl_anon_id`, `_imtbl_sid` | Anonymous analytics without PII | +| `full` | Everything in `anonymous` + user identity (email hash, userId) | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent | + +### Updating consent at runtime + +```javascript +// After cookie banner interaction — upgrade to full +window.__imtbl.push(['consent', 'full']); + +// Or downgrade (purges PII from queue) +window.__imtbl.push(['consent', 'none']); +``` + +## Auto-Tracked Events + +All events fire automatically with no instrumentation required. + +| Event | When it fires | Key properties | +|-------|--------------|----------------| +| `page` | Every page load | UTMs, click IDs (`gclid`, `fbclid`, `ttclid`, `msclkid`, `dclid`, `li_fat_id`), `referral_code`, `landing_page` | +| `session_start` | New session (no active `_imtbl_sid` cookie) | `sessionId` | +| `session_end` | Page unload (`visibilitychange` / `pagehide`) | `sessionId`, `duration` (seconds) | +| `form_submitted` | HTML form submission | `formAction`, `formId`, `formName`, `fieldNames`. `emailHash` at `full` consent only. | +| `link_clicked` | Outbound link click (external domains only) | `linkUrl`, `linkText`, `elementId`, `outbound: true` | + +### Disabling specific auto-capture + +```html + +``` + +## Identity (Optional) + +For sites with user accounts, identify known users at `full` consent: + +```javascript +window.__imtbl.push(['identify', 'user-123', 'passport', { email: 'player@example.com' }]); +``` + +Note: traits passed via `identify` are sent as-is. Email values are only automatically SHA-256 hashed when captured from form submissions via auto-capture (see [Auto-Tracked Events](#auto-tracked-events)). If you pass an email in identify traits, hash it yourself before calling identify if that is required for your use case. + +## Cookies + +| Cookie | Lifetime | Purpose | +|--------|----------|---------| +| `imtbl_anon_id` | 2 years | Anonymous device ID (shared with web SDK) | +| `_imtbl_sid` | 30 minutes (rolling) | Session ID — resets on inactivity | + +Both cookies are first-party (`SameSite=Lax`, `Secure` on HTTPS). + +## Content Security Policy (CSP) + +If your site uses a Content-Security-Policy header, add these origins to the relevant directives: + +``` +script-src ... https://cdn.immutable.com; +connect-src ... https://api.immutable.com; +``` + +These must be added alongside your existing policy values, not replace them. + +For nonce-based CSP, add the nonce to the inline ` +``` + +Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js`) is covered by the `script-src https://cdn.immutable.com` directive. + +## Browser Support + +| Browser | Minimum Version | +|---------|----------------| +| Chrome | 80+ | +| Firefox | 78+ | +| Safari | 14+ | +| Edge | 80+ | + +## Game Page Integration + +The Game Page uses the pixel with `consent: 'anonymous'` (no PII, device signals only): + +```html + +``` + +### Validation Checklist + +After installing on Game Page, verify: + +- [ ] Snippet is in `` and does not block rendering (async load confirmed) +- [ ] Page load impact under 50ms (measure with Lighthouse) +- [ ] `PageMessage` events visible in events pipeline with `surface: 'pixel'` +- [ ] Attribution context (UTMs, referrer) correctly captured on campaign-linked visits +- [ ] Session cookie (`_imtbl_sid`) set and rolling on navigation +- [ ] Anonymous ID cookie (`imtbl_anon_id`) set with 2-year expiry +- [ ] No console errors across Chrome 80+, Firefox 78+, Safari 14+, Edge 80+ +- [ ] CSP (if any) allows `script-src cdn.immutable.com` and `connect-src api.immutable.com` +- [ ] 100% of events pass backend schema validation (check rejected count in API logs) +- [ ] Event volume within expected range — no duplicate events, no runaway listeners +- [ ] Monitor for 24 hours post-deployment before clearing for external rollout + +### Browser Compatibility Matrix + +| Check | Chrome 80+ | Firefox 78+ | Safari 14+ | Edge 80+ | +|-------|-----------|-------------|------------|---------| +| Pixel loads (no network errors) | | | | | +| `page` event fires (POST 200 in Network tab) | | | | | +| `surface: 'pixel'` in request body | | | | | +| `_imtbl_sid` cookie set (30min expiry) | | | | | +| `imtbl_anon_id` cookie set (2yr expiry) | | | | | +| UTM params captured in properties | | | | | +| `form_submitted` fires on form submit | | | | | +| `link_clicked` fires on outbound click | | | | | +| No console errors | | | | | diff --git a/packages/audience/pixel/scripts/validate-cdn.sh b/packages/audience/pixel/scripts/validate-cdn.sh new file mode 100755 index 0000000000..e3a45f5439 --- /dev/null +++ b/packages/audience/pixel/scripts/validate-cdn.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# +# Validate the pixel CDN bundle is deployed and within budget. +# +# Usage: +# ./scripts/validate-cdn.sh [URL] +# +# Defaults to the production CDN URL if no argument is provided. + +set -euo pipefail + +CDN_URL="${1:-https://cdn.immutable.com/pixel/v1/imtbl.js}" +MAX_GZIP_BYTES=10240 # 10 KB — must match bundlebudget.json +WARN_GZIP_BYTES=8192 # 8 KB + +PASS=0 +FAIL=0 + +TMPFILE=$(mktemp) +TMPHEADERS=$(mktemp) +trap 'rm -f "$TMPFILE" "$TMPHEADERS"' EXIT + +pass() { echo " ✓ $1"; PASS=$((PASS + 1)); } +fail() { echo " ✗ $1"; FAIL=$((FAIL + 1)); } + +echo "Validating pixel bundle: $CDN_URL" +echo "" + +# --- Fetch the bundle (single request for body + headers) --- +HTTP_CODE=$(curl -s --connect-timeout 10 --max-time 30 -D "$TMPHEADERS" -o "$TMPFILE" -w '%{http_code}' "$CDN_URL") +CONTENT_TYPE=$(grep -i '^content-type:' "$TMPHEADERS" | tr -d '\r' | awk '{print $2}') + +# --- HTTP status --- +echo "HTTP Response:" +if [ "$HTTP_CODE" = "200" ]; then + pass "Status: $HTTP_CODE" +else + fail "Status: $HTTP_CODE (expected 200)" +fi + +# --- Content-Type --- +if echo "$CONTENT_TYPE" | grep -qi 'javascript'; then + pass "Content-Type: $CONTENT_TYPE" +else + fail "Content-Type: $CONTENT_TYPE (expected application/javascript)" +fi + +# --- Bundle size --- +RAW_BYTES=$(wc -c < "$TMPFILE" | tr -d ' ') +GZIP_BYTES=$(gzip -c "$TMPFILE" | wc -c | tr -d ' ') + +echo "" +echo "Bundle Size:" +echo " Raw: $RAW_BYTES bytes ($(awk -v b="$RAW_BYTES" 'BEGIN{printf "%.1f", b/1024}') KB)" +echo " Gzip: $GZIP_BYTES bytes ($(awk -v b="$GZIP_BYTES" 'BEGIN{printf "%.1f", b/1024}') KB)" + +if [ "$GZIP_BYTES" -le "$MAX_GZIP_BYTES" ]; then + if [ "$GZIP_BYTES" -le "$WARN_GZIP_BYTES" ]; then + pass "Under budget ($GZIP_BYTES / $MAX_GZIP_BYTES bytes gzipped)" + else + pass "Under max budget but above warning threshold ($GZIP_BYTES / $WARN_GZIP_BYTES warn, $MAX_GZIP_BYTES max)" + fi +else + fail "Over budget! $GZIP_BYTES bytes gzipped exceeds $MAX_GZIP_BYTES limit" +fi + +# --- Content markers --- +# These patterns are chosen to avoid false positives in minified code. +echo "" +echo "Content Checks:" +if grep -q '__imtbl' "$TMPFILE"; then + pass "Contains __imtbl global" +else + fail "Missing __imtbl global" +fi + +if grep -q '"pixel"' "$TMPFILE" || grep -q "'pixel'" "$TMPFILE"; then + pass "Contains 'pixel' surface string literal" +else + fail "Missing 'pixel' surface string literal" +fi + +if grep -q 'session_start' "$TMPFILE"; then + pass "Contains session_start event" +else + fail "Missing session_start event" +fi + +if grep -q 'form_submitted' "$TMPFILE"; then + pass "Contains form_submitted event" +else + fail "Missing form_submitted event" +fi + +# --- Summary --- +echo "" +echo "Results: $PASS passed, $FAIL failed" + +if [ "$FAIL" -gt 0 ]; then + exit 1 +fi From a3755ef22b604e9cdd1f79437797904d693e0631 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 10 Apr 2026 16:04:58 +1000 Subject: [PATCH 2/8] docs(pixel): remove identify references from README The identify() method is being removed from the pixel (PR #2846). Update the README to remove the Identity section and adjust the full consent description to reflect that identity signals come only from form submission auto-capture. Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index dc971b5799..7aec6446cc 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -31,7 +31,7 @@ The `consent` option controls what the pixel collects. **Default is `none`** (no |-------|-----------------|-------------|----------| | `none` | Nothing — pixel loads but is inert | None | Before consent banner interaction | | `anonymous` | Device signals, attribution, page views, form submissions, link clicks (no PII) | `imtbl_anon_id`, `_imtbl_sid` | Anonymous analytics without PII | -| `full` | Everything in `anonymous` + user identity (email hash, userId) | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent | +| `full` | Everything in `anonymous` + email hash from form submissions | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent | ### Updating consent at runtime @@ -74,16 +74,6 @@ document.head.appendChild(s); ``` -## Identity (Optional) - -For sites with user accounts, identify known users at `full` consent: - -```javascript -window.__imtbl.push(['identify', 'user-123', 'passport', { email: 'player@example.com' }]); -``` - -Note: traits passed via `identify` are sent as-is. Email values are only automatically SHA-256 hashed when captured from form submissions via auto-capture (see [Auto-Tracked Events](#auto-tracked-events)). If you pass an email in identify traits, hash it yourself before calling identify if that is required for your use case. - ## Cookies | Cookie | Lifetime | Purpose | From 5f15b4b54fff198c86c964a511d79aff6e636738 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 10 Apr 2026 16:09:53 +1000 Subject: [PATCH 3/8] docs(pixel): remove Game Page section and validation checklist from README Keep the README generic for all integrators. Game Page-specific details and the validation checklist are tracked separately. Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 46 ------------------------------- 1 file changed, 46 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 7aec6446cc..f801ab50da 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -120,49 +120,3 @@ Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js | Safari | 14+ | | Edge | 80+ | -## Game Page Integration - -The Game Page uses the pixel with `consent: 'anonymous'` (no PII, device signals only): - -```html - -``` - -### Validation Checklist - -After installing on Game Page, verify: - -- [ ] Snippet is in `` and does not block rendering (async load confirmed) -- [ ] Page load impact under 50ms (measure with Lighthouse) -- [ ] `PageMessage` events visible in events pipeline with `surface: 'pixel'` -- [ ] Attribution context (UTMs, referrer) correctly captured on campaign-linked visits -- [ ] Session cookie (`_imtbl_sid`) set and rolling on navigation -- [ ] Anonymous ID cookie (`imtbl_anon_id`) set with 2-year expiry -- [ ] No console errors across Chrome 80+, Firefox 78+, Safari 14+, Edge 80+ -- [ ] CSP (if any) allows `script-src cdn.immutable.com` and `connect-src api.immutable.com` -- [ ] 100% of events pass backend schema validation (check rejected count in API logs) -- [ ] Event volume within expected range — no duplicate events, no runaway listeners -- [ ] Monitor for 24 hours post-deployment before clearing for external rollout - -### Browser Compatibility Matrix - -| Check | Chrome 80+ | Firefox 78+ | Safari 14+ | Edge 80+ | -|-------|-----------|-------------|------------|---------| -| Pixel loads (no network errors) | | | | | -| `page` event fires (POST 200 in Network tab) | | | | | -| `surface: 'pixel'` in request body | | | | | -| `_imtbl_sid` cookie set (30min expiry) | | | | | -| `imtbl_anon_id` cookie set (2yr expiry) | | | | | -| UTM params captured in properties | | | | | -| `form_submitted` fires on form submit | | | | | -| `link_clicked` fires on outbound click | | | | | -| No console errors | | | | | From c7850e788d9d2bf4df26ff01cfb7716384dcab57 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Mon, 13 Apr 2026 14:09:30 +1000 Subject: [PATCH 4/8] =?UTF-8?q?docs(pixel):=20address=20PR=20feedback=20?= =?UTF-8?q?=E2=80=94=20Hub=20link,=20CMP=20auto-detection=20docs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add link to Immutable Hub for obtaining the publishable key (nattb8) - Document consentMode: 'auto' with GCM v2 and IAB TCF v2 detection, fallback behavior when no CMP is found, and manual override (nattb8) Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index f801ab50da..1b54d86a09 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -19,7 +19,7 @@ document.head.appendChild(s); ``` -Replace `YOUR_PUBLISHABLE_KEY` with your project's publishable key. +Replace `YOUR_PUBLISHABLE_KEY` with your project's publishable key from [Immutable Hub](https://hub.immutable.com/). The script loads asynchronously and does not block page rendering. The default consent level is `none` — the pixel loads but does not collect until consent is explicitly set (see [Consent Modes](#consent-modes)). To start collecting anonymous device signals immediately, add `"consent":"anonymous"` to the init object. @@ -33,8 +33,32 @@ The `consent` option controls what the pixel collects. **Default is `none`** (no | `anonymous` | Device signals, attribution, page views, form submissions, link clicks (no PII) | `imtbl_anon_id`, `_imtbl_sid` | Anonymous analytics without PII | | `full` | Everything in `anonymous` + email hash from form submissions | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent | +### Automatic consent detection + +If your site uses a Consent Management Platform (CMP), the pixel can auto-detect consent state by setting `consentMode` to `'auto'`: + +```html +w[i].push(["init",{"key":"YOUR_KEY","consentMode":"auto"}]); +``` + +The pixel checks for these CMP standards (in priority order): + +1. **Google Consent Mode v2** — reads `analytics_storage` and `ad_storage` from `window.dataLayer` +2. **IAB TCF v2** — reads purpose consents via `window.__tcfapi` + +When `consentMode` is `'auto'`, the pixel starts in `none` and upgrades automatically once a CMP is detected. It also listens for ongoing consent changes (e.g. when a user updates their cookie preferences). + +If no CMP is detected after a few seconds, the pixel remains in `none`. You can provide a manual fallback by pushing a `consent` command: + +```javascript +// Manual fallback if no CMP is present +window.__imtbl.push(['consent', 'anonymous']); +``` + ### Updating consent at runtime +If you are not using `consentMode: 'auto'`, you can set consent manually at any time: + ```javascript // After cookie banner interaction — upgrade to full window.__imtbl.push(['consent', 'full']); From e5c7ee73fa098e2190c45970826dead2be53ed91 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Mon, 13 Apr 2026 14:19:57 +1000 Subject: [PATCH 5/8] docs(pixel): link to external GCM v2 and IAB TCF v2 docs Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 1b54d86a09..ede214b3b2 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -43,8 +43,8 @@ w[i].push(["init",{"key":"YOUR_KEY","consentMode":"auto"}]); The pixel checks for these CMP standards (in priority order): -1. **Google Consent Mode v2** — reads `analytics_storage` and `ad_storage` from `window.dataLayer` -2. **IAB TCF v2** — reads purpose consents via `window.__tcfapi` +1. [**Google Consent Mode v2**](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced) — reads `analytics_storage` and `ad_storage` from `window.dataLayer` +2. [**IAB TCF v2**](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md) — reads purpose consents via `window.__tcfapi` When `consentMode` is `'auto'`, the pixel starts in `none` and upgrades automatically once a CMP is detected. It also listens for ongoing consent changes (e.g. when a user updates their cookie preferences). From 9d4027caf321ba504fd3691648ade3df1a7dea36 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Mon, 13 Apr 2026 14:23:21 +1000 Subject: [PATCH 6/8] docs(pixel): polish README for external readability - Add value prop (campaign measurement, player attribution) - Use diff blocks for init-line changes instead of ambiguous fragments - Clarify consent vs consentMode are mutually exclusive - Improve full consent description (identity matching context) - Remove trailing blank line Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index ede214b3b2..2e6b93c57e 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -1,6 +1,6 @@ # @imtbl/pixel — Immutable Tracking Pixel -A drop-in JavaScript snippet that captures device signals, page views, and attribution data for Immutable's events pipeline. Zero configuration beyond a publishable key. +A drop-in JavaScript snippet that captures device signals, page views, and attribution data for Immutable's events pipeline. Use it to measure campaign performance and attribute player acquisition across your marketing sites, landing pages, and web shops. Zero configuration beyond a publishable key. ## Quick Start @@ -21,7 +21,12 @@ document.head.appendChild(s); Replace `YOUR_PUBLISHABLE_KEY` with your project's publishable key from [Immutable Hub](https://hub.immutable.com/). -The script loads asynchronously and does not block page rendering. The default consent level is `none` — the pixel loads but does not collect until consent is explicitly set (see [Consent Modes](#consent-modes)). To start collecting anonymous device signals immediately, add `"consent":"anonymous"` to the init object. +The script loads asynchronously and does not block page rendering. The default consent level is `none` — the pixel loads but does not collect until consent is explicitly set (see [Consent Modes](#consent-modes)). To start collecting anonymous device signals immediately, add `"consent":"anonymous"` to the init options: + +```diff +- w[i].push(["init",{"key":"YOUR_PUBLISHABLE_KEY"}]); ++ w[i].push(["init",{"key":"YOUR_PUBLISHABLE_KEY","consent":"anonymous"}]); +``` ## Consent Modes @@ -31,16 +36,19 @@ The `consent` option controls what the pixel collects. **Default is `none`** (no |-------|-----------------|-------------|----------| | `none` | Nothing — pixel loads but is inert | None | Before consent banner interaction | | `anonymous` | Device signals, attribution, page views, form submissions, link clicks (no PII) | `imtbl_anon_id`, `_imtbl_sid` | Anonymous analytics without PII | -| `full` | Everything in `anonymous` + email hash from form submissions | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent | +| `full` | Everything in `anonymous` + hashed email capture from form submissions (for identity matching) | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent for marketing/ads | ### Automatic consent detection -If your site uses a Consent Management Platform (CMP), the pixel can auto-detect consent state by setting `consentMode` to `'auto'`: +If your site uses a Consent Management Platform (CMP), the pixel can auto-detect consent state. Set `consentMode` to `'auto'` instead of setting `consent` directly: -```html -w[i].push(["init",{"key":"YOUR_KEY","consentMode":"auto"}]); +```diff +- w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]); ++ w[i].push(["init",{"key":"YOUR_KEY","consentMode":"auto"}]); ``` +> **Note:** `consentMode` and `consent` are mutually exclusive. When `consentMode` is `'auto'`, the pixel ignores the `consent` option and starts in `none` until a CMP is detected. + The pixel checks for these CMP standards (in priority order): 1. [**Google Consent Mode v2**](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced) — reads `analytics_storage` and `ad_storage` from `window.dataLayer` @@ -143,4 +151,3 @@ Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js | Firefox | 78+ | | Safari | 14+ | | Edge | 80+ | - From d6906ff2f940b00988e14d8413b9a632721ce9e7 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Mon, 13 Apr 2026 14:41:32 +1000 Subject: [PATCH 7/8] docs(pixel): deduplicate consent auto-detection description Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 2e6b93c57e..01a6b31048 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -47,16 +47,14 @@ If your site uses a Consent Management Platform (CMP), the pixel can auto-detect + w[i].push(["init",{"key":"YOUR_KEY","consentMode":"auto"}]); ``` -> **Note:** `consentMode` and `consent` are mutually exclusive. When `consentMode` is `'auto'`, the pixel ignores the `consent` option and starts in `none` until a CMP is detected. +> **Note:** `consentMode` and `consent` are mutually exclusive — do not set both. -The pixel checks for these CMP standards (in priority order): +The pixel starts in `none` and checks for these CMP standards (in priority order): 1. [**Google Consent Mode v2**](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced) — reads `analytics_storage` and `ad_storage` from `window.dataLayer` 2. [**IAB TCF v2**](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md) — reads purpose consents via `window.__tcfapi` -When `consentMode` is `'auto'`, the pixel starts in `none` and upgrades automatically once a CMP is detected. It also listens for ongoing consent changes (e.g. when a user updates their cookie preferences). - -If no CMP is detected after a few seconds, the pixel remains in `none`. You can provide a manual fallback by pushing a `consent` command: +Once a CMP is detected, the pixel upgrades consent automatically and continues listening for changes (e.g. when a user updates their cookie preferences). If no CMP is detected after a few seconds, the pixel remains in `none`. You can provide a manual fallback: ```javascript // Manual fallback if no CMP is present From 7167d049d45b1adc3d68179e395a886405af31b3 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Mon, 13 Apr 2026 16:22:03 +1000 Subject: [PATCH 8/8] docs(pixel): clarify no failure callback for CMP auto-detection Make it explicit that there is no callback when CMP detection times out, and show a setTimeout-based fallback pattern. Co-Authored-By: Claude Opus 4.6 --- packages/audience/pixel/README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 01a6b31048..9f6b52a653 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -54,11 +54,12 @@ The pixel starts in `none` and checks for these CMP standards (in priority order 1. [**Google Consent Mode v2**](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced) — reads `analytics_storage` and `ad_storage` from `window.dataLayer` 2. [**IAB TCF v2**](https://github.com/InteractiveAdvertisingBureau/GDPR-Transparency-and-Consent-Framework/blob/master/TCFv2/IAB%20Tech%20Lab%20-%20CMP%20API%20v2.md) — reads purpose consents via `window.__tcfapi` -Once a CMP is detected, the pixel upgrades consent automatically and continues listening for changes (e.g. when a user updates their cookie preferences). If no CMP is detected after a few seconds, the pixel remains in `none`. You can provide a manual fallback: +Once a CMP is detected, the pixel upgrades consent automatically and continues listening for changes (e.g. when a user updates their cookie preferences). If no CMP is detected after ~2.5 seconds, the pixel remains in `none` silently (there is no failure callback). If your CMP may not be present on every page, push a manual fallback on your own timeout: ```javascript -// Manual fallback if no CMP is present -window.__imtbl.push(['consent', 'anonymous']); +setTimeout(function() { + window.__imtbl.push(['consent', 'anonymous']); +}, 3000); ``` ### Updating consent at runtime