From 13e8cf5a6d8d8f551555af708968ecd8cbebbc27 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Wed, 13 May 2026 14:34:27 +1000 Subject: [PATCH 1/8] feat(audience): remove sandbox env routing, add testMode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removes getBaseUrl() and the pk_imapik-test- prefix check that routed events to api.sandbox.immutable.com, which is being decommissioned. Adds testMode init option to both the pixel and SDK — when true, every outbound event carries a top-level test: true field so the backend can filter test data from production analytics. Co-Authored-By: Claude Sonnet 4.6 --- packages/audience/core/src/config.test.ts | 15 --------------- packages/audience/core/src/config.ts | 8 +------- packages/audience/core/src/consent.ts | 4 ++-- packages/audience/core/src/queue.ts | 4 ++-- packages/audience/core/src/types.ts | 2 ++ packages/audience/pixel/README.md | 17 +++++++++++++++++ packages/audience/pixel/src/pixel.ts | 7 ++++++- packages/audience/sdk/README.md | 12 ++++++++++++ packages/audience/sdk/src/sdk.ts | 4 ++++ packages/audience/sdk/src/types.ts | 2 ++ 10 files changed, 48 insertions(+), 27 deletions(-) delete mode 100644 packages/audience/core/src/config.test.ts diff --git a/packages/audience/core/src/config.test.ts b/packages/audience/core/src/config.test.ts deleted file mode 100644 index f1ef56c1ff..0000000000 --- a/packages/audience/core/src/config.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { getBaseUrl } from './config'; - -describe('getBaseUrl', () => { - it('returns sandbox URL for test keys', () => { - expect(getBaseUrl('pk_imapik-test-local')).toBe('https://api.sandbox.immutable.com'); - }); - - it('returns production URL for live keys', () => { - expect(getBaseUrl('pk_imapik-abcdef123')).toBe('https://api.immutable.com'); - }); - - it('returns production URL for empty key', () => { - expect(getBaseUrl('')).toBe('https://api.immutable.com'); - }); -}); diff --git a/packages/audience/core/src/config.ts b/packages/audience/core/src/config.ts index 2105170d3e..d125edacf8 100644 --- a/packages/audience/core/src/config.ts +++ b/packages/audience/core/src/config.ts @@ -1,4 +1,4 @@ -const TEST_KEY_PREFIX = 'pk_imapik-test-'; +export const BASE_URL = 'https://api.immutable.com'; export const INGEST_PATH = '/v1/audience/messages'; export const CONSENT_PATH = '/v1/audience/tracking-consent'; @@ -13,9 +13,3 @@ export const SESSION_MAX_AGE = 30 * 60; // 30 minutes in seconds export const SESSION_START = 'session_start'; export const SESSION_END = 'session_end'; - -export const getBaseUrl = (publishableKey: string): string => ( - publishableKey.startsWith(TEST_KEY_PREFIX) - ? 'https://api.sandbox.immutable.com' - : 'https://api.immutable.com' -); diff --git a/packages/audience/core/src/consent.ts b/packages/audience/core/src/consent.ts index 53f78fdc49..3027591b5c 100644 --- a/packages/audience/core/src/consent.ts +++ b/packages/audience/core/src/consent.ts @@ -4,7 +4,7 @@ import type { import type { MessageQueue } from './queue'; import type { HttpSend } from './transport'; import { type AudienceError, invokeOnError, toAudienceError } from './errors'; -import { CONSENT_PATH, getBaseUrl } from './config'; +import { BASE_URL, CONSENT_PATH } from './config'; export interface ConsentManager { level: ConsentLevel; @@ -59,7 +59,7 @@ export function createConsentManager( const LEVELS: Record = { none: 0, anonymous: 1, full: 2 }; function notifyBackend(level: ConsentLevel): void { - const url = `${baseUrl ?? getBaseUrl(publishableKey)}${CONSENT_PATH}`; + const url = `${baseUrl ?? BASE_URL}${CONSENT_PATH}`; const payload: ConsentUpdatePayload = { anonymousId, status: level, source }; // Fire-and-forget. HttpSend never rejects, so the floating chain is safe. send(url, publishableKey, payload, { method: 'PUT', keepalive: true }) diff --git a/packages/audience/core/src/queue.ts b/packages/audience/core/src/queue.ts index f5eb1a112c..7013182ca7 100644 --- a/packages/audience/core/src/queue.ts +++ b/packages/audience/core/src/queue.ts @@ -2,7 +2,7 @@ import type { Message, BatchPayload } from './types'; import type { HttpSend } from './transport'; import { type AudienceError, invokeOnError, toAudienceError } from './errors'; import { - getBaseUrl, INGEST_PATH, FLUSH_INTERVAL_MS, FLUSH_SIZE, + BASE_URL, INGEST_PATH, FLUSH_INTERVAL_MS, FLUSH_SIZE, } from './config'; import * as storage from './storage'; import { isBrowser } from './utils'; @@ -87,7 +87,7 @@ export class MessageQueue { private readonly publishableKey: string, options?: MessageQueueOptions, ) { - this.endpointUrl = `${options?.baseUrl ?? getBaseUrl(publishableKey)}${INGEST_PATH}`; + this.endpointUrl = `${options?.baseUrl ?? BASE_URL}${INGEST_PATH}`; this.flushIntervalMs = options?.flushIntervalMs ?? FLUSH_INTERVAL_MS; this.flushSize = options?.flushSize ?? FLUSH_SIZE; this.onFlush = options?.onFlush; diff --git a/packages/audience/core/src/types.ts b/packages/audience/core/src/types.ts index 9e9e39aff2..f26bedfcbb 100644 --- a/packages/audience/core/src/types.ts +++ b/packages/audience/core/src/types.ts @@ -28,6 +28,8 @@ interface BaseMessage { anonymousId: string; surface: Surface; context: EventContext; + /** Present when the SDK/pixel is initialised with testMode: true. */ + test?: true; } export interface TrackMessage extends BaseMessage { diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 6ebd349f6b..e7f1ee1102 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -115,6 +115,23 @@ document.head.appendChild(s); Both cookies are first-party (`SameSite=Lax`, `Secure` on HTTPS). +## Test Mode + +Set `testMode: true` when initialising the pixel in non-production environments (dev, staging). All events will include a top-level `test: true` field so they can be filtered from production analytics. + +```html + +``` + ## Content Security Policy (CSP) If your site uses a Content-Security-Policy header, add these origins to the relevant directives: diff --git a/packages/audience/pixel/src/pixel.ts b/packages/audience/pixel/src/pixel.ts index 3acc92a6cf..dd477fe734 100644 --- a/packages/audience/pixel/src/pixel.ts +++ b/packages/audience/pixel/src/pixel.ts @@ -38,6 +38,8 @@ export interface PixelInitOptions { autocapture?: AutocaptureOptions; /** Override the default API base URL. */ baseUrl?: string; + /** When true, all events are marked test: true and can be filtered from production analytics. */ + testMode?: boolean; } export class Pixel { @@ -55,6 +57,8 @@ export class Pixel { private domain: string | undefined; + private testMode = false; + private initialized = false; private unloadHandler?: () => void; @@ -80,6 +84,7 @@ export class Pixel { this.publishableKey = key; this.domain = domain; + this.testMode = options.testMode ?? false; this.queue = new MessageQueue( httpSend, @@ -320,7 +325,6 @@ export class Pixel { // -- Helpers ------------------------------------------------------------ - // eslint-disable-next-line class-methods-use-this private buildBase() { return { messageId: generateId(), @@ -328,6 +332,7 @@ export class Pixel { anonymousId: this.anonymousId, surface: 'pixel' as const, context: collectContext('@imtbl/pixel', PIXEL_VERSION), + ...(this.testMode && { test: true as const }), }; } diff --git a/packages/audience/sdk/README.md b/packages/audience/sdk/README.md index 0972cab28b..3c248d3adb 100644 --- a/packages/audience/sdk/README.md +++ b/packages/audience/sdk/README.md @@ -61,6 +61,18 @@ audience.shutdown(); ``` +## Test Mode + +Pass `testMode: true` when initialising the SDK in non-production environments (dev, staging). All events will include a top-level `test: true` field so they can be filtered from production analytics. + +```ts +const audience = Audience.init({ + publishableKey: 'YOUR_PUBLISHABLE_KEY', + consent: 'anonymous', + testMode: true, +}); +``` + ## Documentation - [Web SDK](https://docs.immutable.com/docs/products/audience/web-sdk) — API reference, usage, integration walkthrough diff --git a/packages/audience/sdk/src/sdk.ts b/packages/audience/sdk/src/sdk.ts index bb6b031dbb..a7fef1041c 100644 --- a/packages/audience/sdk/src/sdk.ts +++ b/packages/audience/sdk/src/sdk.ts @@ -60,6 +60,8 @@ export class Audience { private readonly cookieDomain?: string; + private readonly testMode: boolean; + private anonymousId: string; private sessionId: string | undefined; @@ -81,6 +83,7 @@ export class Audience { const consentSource = DEFAULT_CONSENT_SOURCE; this.cookieDomain = cookieDomain; + this.testMode = config.testMode ?? false; this.debug = new DebugLogger(config.debug ?? false); let isNewSession = false; @@ -173,6 +176,7 @@ export class Audience { anonymousId: this.anonymousId, surface: 'web' as const, context: collectContext(LIBRARY_NAME, LIBRARY_VERSION), + ...(this.testMode && { test: true as const }), }; } diff --git a/packages/audience/sdk/src/types.ts b/packages/audience/sdk/src/types.ts index 92a85f072c..54653042da 100644 --- a/packages/audience/sdk/src/types.ts +++ b/packages/audience/sdk/src/types.ts @@ -16,6 +16,8 @@ export interface AudienceConfig { flushSize?: number; /** Override the default API base URL. */ baseUrl?: string; + /** When true, all events are marked test: true and can be filtered from production analytics. */ + testMode?: boolean; /** * Called when the SDK fails to reach the backend. Receives a structured * {@link AudienceError} with a machine-readable `code` so studios can From 484a5fd20f908ed8f557d84ad7f816d138937360 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Wed, 13 May 2026 15:50:25 +1000 Subject: [PATCH 2/8] fix(audience): update tests for prod URL, drop testMode from public READMEs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates the 8 stale sandbox URL assertions in consent.test.ts and queue.test.ts that bugbot flagged — they referenced api.sandbox URLs that no longer get produced after the getBaseUrl removal. Drops the Test Mode README sections. The option is for internal/non-prod use, similar to baseUrl which is also undocumented. Still discoverable via TypeScript IntelliSense. Co-Authored-By: Claude Sonnet 4.6 --- packages/audience/core/src/consent.test.ts | 6 +++--- packages/audience/core/src/queue.test.ts | 10 +++++----- packages/audience/pixel/README.md | 17 ----------------- packages/audience/sdk/README.md | 12 ------------ 4 files changed, 8 insertions(+), 37 deletions(-) diff --git a/packages/audience/core/src/consent.test.ts b/packages/audience/core/src/consent.test.ts index 4d5667d556..8c581e74f4 100644 --- a/packages/audience/core/src/consent.test.ts +++ b/packages/audience/core/src/consent.test.ts @@ -110,7 +110,7 @@ describe('createConsentManager', () => { manager.setLevel('anonymous'); expect(send).toHaveBeenCalledWith( - 'https://api.sandbox.immutable.com/v1/audience/tracking-consent', + 'https://api.immutable.com/v1/audience/tracking-consent', 'pk_imapik-test-local', { anonymousId: 'anon-1', status: 'anonymous', source: 'pixel' }, { method: 'PUT', keepalive: true }, @@ -146,7 +146,7 @@ describe('createConsentManager', () => { ok: false, error: new TransportError({ status: 503, - endpoint: 'https://api.sandbox.immutable.com/v1/audience/tracking-consent', + endpoint: 'https://api.immutable.com/v1/audience/tracking-consent', body: { code: 'SERVICE_UNAVAILABLE' }, }), }); @@ -172,7 +172,7 @@ describe('createConsentManager', () => { ok: false, error: new TransportError({ status: 0, - endpoint: 'https://api.sandbox.immutable.com/v1/audience/tracking-consent', + endpoint: 'https://api.immutable.com/v1/audience/tracking-consent', cause: new TypeError('Failed to fetch'), }), }); diff --git a/packages/audience/core/src/queue.test.ts b/packages/audience/core/src/queue.test.ts index 539d94a570..da9d515fe7 100644 --- a/packages/audience/core/src/queue.test.ts +++ b/packages/audience/core/src/queue.test.ts @@ -206,7 +206,7 @@ describe('MessageQueue', () => { const send = jest.fn, Parameters>().mockResolvedValue({ ok: false, error: new TransportError({ - status: 500, endpoint: 'https://api.sandbox.immutable.com/v1/audience/messages', body: null, + status: 500, endpoint: 'https://api.immutable.com/v1/audience/messages', body: null, }), }); const queue = createQueue(send, { onError }); @@ -228,7 +228,7 @@ describe('MessageQueue', () => { ok: false, error: new TransportError({ status: 0, - endpoint: 'https://api.sandbox.immutable.com/v1/audience/messages', + endpoint: 'https://api.immutable.com/v1/audience/messages', cause: new TypeError('Failed to fetch'), }), }); @@ -278,7 +278,7 @@ describe('MessageQueue', () => { ok: false, error: new TransportError({ status: 200, - endpoint: 'https://api.sandbox.immutable.com/v1/audience/messages', + endpoint: 'https://api.immutable.com/v1/audience/messages', body: { accepted: 1, rejected: 1 }, }), }); @@ -342,7 +342,7 @@ describe('page-unload flush (keepalive)', () => { document.dispatchEvent(new Event('visibilitychange')); expect(send).toHaveBeenCalledWith( - 'https://api.sandbox.immutable.com/v1/audience/messages', + 'https://api.immutable.com/v1/audience/messages', 'pk_imapik-test-local', expect.objectContaining({ messages: expect.any(Array) }), { keepalive: true }, @@ -366,7 +366,7 @@ describe('page-unload flush (keepalive)', () => { window.dispatchEvent(new Event('pagehide')); expect(send).toHaveBeenCalledWith( - 'https://api.sandbox.immutable.com/v1/audience/messages', + 'https://api.immutable.com/v1/audience/messages', 'pk_imapik-test-local', expect.objectContaining({ messages: expect.any(Array) }), { keepalive: true }, diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index e7f1ee1102..6ebd349f6b 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -115,23 +115,6 @@ document.head.appendChild(s); Both cookies are first-party (`SameSite=Lax`, `Secure` on HTTPS). -## Test Mode - -Set `testMode: true` when initialising the pixel in non-production environments (dev, staging). All events will include a top-level `test: true` field so they can be filtered from production analytics. - -```html - -``` - ## Content Security Policy (CSP) If your site uses a Content-Security-Policy header, add these origins to the relevant directives: diff --git a/packages/audience/sdk/README.md b/packages/audience/sdk/README.md index 3c248d3adb..0972cab28b 100644 --- a/packages/audience/sdk/README.md +++ b/packages/audience/sdk/README.md @@ -61,18 +61,6 @@ audience.shutdown(); ``` -## Test Mode - -Pass `testMode: true` when initialising the SDK in non-production environments (dev, staging). All events will include a top-level `test: true` field so they can be filtered from production analytics. - -```ts -const audience = Audience.init({ - publishableKey: 'YOUR_PUBLISHABLE_KEY', - consent: 'anonymous', - testMode: true, -}); -``` - ## Documentation - [Web SDK](https://docs.immutable.com/docs/products/audience/web-sdk) — API reference, usage, integration walkthrough From 86d0a34cd5affdce8e95a4f82a736c8a20ae9145 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 15 May 2026 12:00:27 +1000 Subject: [PATCH 3/8] docs(audience): add brief testMode mention to READMEs Co-Authored-By: Claude Opus 4.7 --- packages/audience/pixel/README.md | 2 ++ packages/audience/sdk/README.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 6ebd349f6b..9016891bdc 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -152,6 +152,8 @@ Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js | Safari | 14+ | | Edge | 80+ | +Pass `"testMode": true` in the init options to mark all events with `test: true`, which lets you filter test traffic from production analytics. + ## Documentation - [Tracking Pixel](https://docs.immutable.com/docs/products/audience/tracking-pixel) — this package (setup, consent modes, auto-tracked events) diff --git a/packages/audience/sdk/README.md b/packages/audience/sdk/README.md index 0972cab28b..657bb4bacd 100644 --- a/packages/audience/sdk/README.md +++ b/packages/audience/sdk/README.md @@ -61,6 +61,8 @@ audience.shutdown(); ``` +Pass `testMode: true` in the config to mark all events with `test: true`, which lets you filter test traffic from production analytics. + ## Documentation - [Web SDK](https://docs.immutable.com/docs/products/audience/web-sdk) — API reference, usage, integration walkthrough From f094f65dc92a515c6d835103618ae4231948c794 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 15 May 2026 12:55:01 +1000 Subject: [PATCH 4/8] feat(audience): update sample app for sandbox removal + testMode - Remove api.sandbox.immutable.com from CSP and environment dropdown - Add testMode checkbox in Advanced section, wired into Audience.init() - Update package.json description Co-Authored-By: Claude Opus 4.7 --- packages/audience/sdk-sample-app/index.html | 7 +++++-- packages/audience/sdk-sample-app/package.json | 2 +- packages/audience/sdk-sample-app/sample-app.js | 5 ++++- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/audience/sdk-sample-app/index.html b/packages/audience/sdk-sample-app/index.html index cd325bedcd..138a852ece 100644 --- a/packages/audience/sdk-sample-app/index.html +++ b/packages/audience/sdk-sample-app/index.html @@ -4,7 +4,7 @@ + content="default-src 'self'; script-src 'self'; style-src 'self'; connect-src https://api.dev.immutable.com https://api.immutable.com"> Immutable Audience SDK — Sample App @@ -58,7 +58,6 @@

@@ -78,6 +77,10 @@

Mirror SDK internal log output into the in-page event log below.

+
+ +

Mark all events with test: true so they can be filtered from production analytics.

+
diff --git a/packages/audience/sdk-sample-app/package.json b/packages/audience/sdk-sample-app/package.json index d8172d57bd..04ec7e0f32 100644 --- a/packages/audience/sdk-sample-app/package.json +++ b/packages/audience/sdk-sample-app/package.json @@ -1,6 +1,6 @@ { "name": "@imtbl/audience-sdk-sample-app", - "description": "Interactive sample app for @imtbl/audience. Exercises every public method on the Audience class, every typed track() event, and every reachable AudienceErrorCode against the sandbox backend.", + "description": "Interactive sample app for @imtbl/audience. Exercises every public method on the Audience class, every typed track() event, and every reachable AudienceErrorCode.", "version": "0.0.0", "author": "Immutable", "private": true, diff --git a/packages/audience/sdk-sample-app/sample-app.js b/packages/audience/sdk-sample-app/sample-app.js index 1a4a93a045..8df8e2d3be 100644 --- a/packages/audience/sdk-sample-app/sample-app.js +++ b/packages/audience/sdk-sample-app/sample-app.js @@ -223,6 +223,7 @@ ['pk', 'value', 'pk'], ['initial-consent', 'value', 'initialConsent'], ['debug', 'checked', 'debug'], + ['test-mode', 'checked', 'testMode'], ['cookie-domain', 'value', 'cookieDomain'], ['flush-interval', 'value', 'flushInterval'], ['flush-size', 'value', 'flushSize'], @@ -329,6 +330,7 @@ debug: $('debug').checked, onError: handleError, }; + if ($('test-mode').checked) config.testMode = true; var cd = $('cookie-domain').value.trim(); if (cd) config.cookieDomain = cd; config.baseUrl = $('environment').value; var fi = parseInt($('flush-interval').value, 10); if (!Number.isNaN(fi)) config.flushInterval = fi; @@ -417,6 +419,7 @@ log('INIT', { consent: config.consent, debug: config.debug, + testMode: config.testMode || false, cookieDomain: config.cookieDomain, flushInterval: config.flushInterval, flushSize: config.flushSize, @@ -545,7 +548,7 @@ }); $('environment').addEventListener('change', function () { updateStatus(); saveUiState(); }); $('initial-consent').addEventListener('change', function () { saveUiState(); updateStatus(); }); - ['debug', 'cookie-domain', 'flush-interval', 'flush-size'].forEach(function (id) { + ['debug', 'test-mode', 'cookie-domain', 'flush-interval', 'flush-size'].forEach(function (id) { var el = $(id); if (!el) return; el.addEventListener('input', saveUiState); From 7b35e2fa0aa14b8b2797ef900f1f2184e743d950 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 15 May 2026 13:00:06 +1000 Subject: [PATCH 5/8] docs(audience): promote testMode note to its own section in READMEs Co-Authored-By: Claude Opus 4.7 --- packages/audience/pixel/README.md | 12 +++++++++++- packages/audience/sdk/README.md | 14 +++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 9016891bdc..5644e1b69c 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -152,7 +152,17 @@ Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js | Safari | 14+ | | Edge | 80+ | -Pass `"testMode": true` in the init options to mark all events with `test: true`, which lets you filter test traffic from production analytics. +## Test mode + +Set `"testMode": true` in the init options to mark every event with a +top-level `test: true` flag. Useful for development, staging, or QA — +events still flow through the production endpoint, but can be filtered +out of production analytics downstream. + +```diff +- w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]); ++ w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous","testMode":true}]); +``` ## Documentation diff --git a/packages/audience/sdk/README.md b/packages/audience/sdk/README.md index 657bb4bacd..29703e941b 100644 --- a/packages/audience/sdk/README.md +++ b/packages/audience/sdk/README.md @@ -61,7 +61,19 @@ audience.shutdown(); ``` -Pass `testMode: true` in the config to mark all events with `test: true`, which lets you filter test traffic from production analytics. +## Test mode + +Pass `testMode: true` in the config to mark every event with a top-level +`test: true` flag. Useful for development, staging, or QA — events still +flow through the production endpoint, but can be filtered out of +production analytics downstream. + +```ts +Audience.init({ + publishableKey: 'YOUR_PUBLISHABLE_KEY', + testMode: true, +}); +``` ## Documentation From 62f256b64e4afc564544b952c50d256bafbbd6c4 Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 15 May 2026 13:07:25 +1000 Subject: [PATCH 6/8] docs(audience): move pixel Test mode section to sit with other init options Group it with the other configuration sections (Consent Modes, Auto-Tracked Events) instead of orphaning it after the operational details (Cookies, CSP, Browser Support). Co-Authored-By: Claude Opus 4.7 --- packages/audience/pixel/README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index 5644e1b69c..c112a571b6 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -106,6 +106,18 @@ document.head.appendChild(s); ``` +## Test mode + +Set `"testMode": true` in the init options to mark every event with a +top-level `test: true` flag. Useful for development, staging, or QA — +events still flow through the production endpoint, but can be filtered +out of production analytics downstream. + +```diff +- w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]); ++ w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous","testMode":true}]); +``` + ## Cookies | Cookie | Lifetime | Purpose | @@ -152,18 +164,6 @@ Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js | Safari | 14+ | | Edge | 80+ | -## Test mode - -Set `"testMode": true` in the init options to mark every event with a -top-level `test: true` flag. Useful for development, staging, or QA — -events still flow through the production endpoint, but can be filtered -out of production analytics downstream. - -```diff -- w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]); -+ w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous","testMode":true}]); -``` - ## Documentation - [Tracking Pixel](https://docs.immutable.com/docs/products/audience/tracking-pixel) — this package (setup, consent modes, auto-tracked events) From 36497368e02af1bc6d04841fb92a51c8d2f9546b Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 15 May 2026 13:10:58 +1000 Subject: [PATCH 7/8] docs(audience): drop Test mode section from Web SDK README MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Web SDK README documents zero init options inline — everything is shown in the quickstart snippet and otherwise deferred to the docs site. Singling out testMode violated that pattern. Pixel README keeps its Test mode section, matching its existing convention of documenting consent / autocapture options inline. Co-Authored-By: Claude Opus 4.7 --- packages/audience/sdk/README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/packages/audience/sdk/README.md b/packages/audience/sdk/README.md index 29703e941b..0972cab28b 100644 --- a/packages/audience/sdk/README.md +++ b/packages/audience/sdk/README.md @@ -61,20 +61,6 @@ audience.shutdown(); ``` -## Test mode - -Pass `testMode: true` in the config to mark every event with a top-level -`test: true` flag. Useful for development, staging, or QA — events still -flow through the production endpoint, but can be filtered out of -production analytics downstream. - -```ts -Audience.init({ - publishableKey: 'YOUR_PUBLISHABLE_KEY', - testMode: true, -}); -``` - ## Documentation - [Web SDK](https://docs.immutable.com/docs/products/audience/web-sdk) — API reference, usage, integration walkthrough From ad0f60490872117e0406a01f721b62d98304770c Mon Sep 17 00:00:00 2001 From: Ben Booth Date: Fri, 15 May 2026 13:34:28 +1000 Subject: [PATCH 8/8] docs(audience): strip em-dashes and AI tells from pixel README Co-Authored-By: Claude Opus 4.7 --- packages/audience/pixel/README.md | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/audience/pixel/README.md b/packages/audience/pixel/README.md index c112a571b6..d1ad7cb04c 100644 --- a/packages/audience/pixel/README.md +++ b/packages/audience/pixel/README.md @@ -1,4 +1,4 @@ -# @imtbl/pixel — Immutable Tracking Pixel +# @imtbl/pixel 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. @@ -21,7 +21,7 @@ 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 options: +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"}]); @@ -34,7 +34,7 @@ The `consent` option controls what the pixel collects. **Default is `none`** (no | Level | What's collected | Cookies set | Use case | |-------|-----------------|-------------|----------| -| `none` | Nothing — pixel loads but is inert | None | Before consent banner interaction | +| `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` + hashed email capture from form submissions (for identity matching) | `imtbl_anon_id`, `_imtbl_sid` | After explicit user consent for marketing/ads | @@ -47,12 +47,12 @@ 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 — do not set both. +> **Note:** `consentMode` and `consent` are mutually exclusive. Do not set both. 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` +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 ~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: @@ -67,7 +67,7 @@ setTimeout(function() { If you are not using `consentMode: 'auto'`, you can set consent manually at any time: ```javascript -// After cookie banner interaction — upgrade to full +// After cookie banner interaction, upgrade to full window.__imtbl.push(['consent', 'full']); // Or downgrade (purges PII from queue) @@ -109,9 +109,8 @@ document.head.appendChild(s); ## Test mode Set `"testMode": true` in the init options to mark every event with a -top-level `test: true` flag. Useful for development, staging, or QA — -events still flow through the production endpoint, but can be filtered -out of production analytics downstream. +top-level `test: true` flag. Test events still flow through the +production endpoint, but can be filtered out of production analytics. ```diff - w[i].push(["init",{"key":"YOUR_KEY","consent":"anonymous"}]); @@ -123,7 +122,7 @@ out of production analytics downstream. | 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 | +| `_imtbl_sid` | 30 minutes (rolling) | Session ID (resets on inactivity) | Both cookies are first-party (`SameSite=Lax`, `Secure` on HTTPS). @@ -166,7 +165,7 @@ Note: the nonce covers the inline snippet only. The CDN-loaded script (`imtbl.js ## Documentation -- [Tracking Pixel](https://docs.immutable.com/docs/products/audience/tracking-pixel) — this package (setup, consent modes, auto-tracked events) -- [Web SDK](https://docs.immutable.com/docs/products/audience/web-sdk) — sibling `@imtbl/audience` package for typed event tracking and identity management -- [REST API](https://docs.immutable.com/docs/products/audience/rest-api) — backend reference for direct integration -- [Data dictionary](https://docs.immutable.com/docs/products/audience/data-dictionary) — predefined event names and property schemas +- [Tracking Pixel](https://docs.immutable.com/docs/products/audience/tracking-pixel): this package (setup, consent modes, auto-tracked events) +- [Web SDK](https://docs.immutable.com/docs/products/audience/web-sdk): sibling `@imtbl/audience` package for typed event tracking and identity management +- [REST API](https://docs.immutable.com/docs/products/audience/rest-api): backend reference for direct integration +- [Data dictionary](https://docs.immutable.com/docs/products/audience/data-dictionary): predefined event names and property schemas