@datalyr/web 1.7.6 → 1.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +542 -476
  2. package/dist/attribution-lyr.test.d.ts +2 -0
  3. package/dist/attribution-lyr.test.d.ts.map +1 -0
  4. package/dist/attribution-oppref.test.d.ts +2 -0
  5. package/dist/attribution-oppref.test.d.ts.map +1 -0
  6. package/dist/attribution.d.ts.map +1 -1
  7. package/dist/config.d.ts +1 -0
  8. package/dist/config.d.ts.map +1 -1
  9. package/dist/datalyr.cjs.js +751 -46
  10. package/dist/datalyr.cjs.js.map +1 -1
  11. package/dist/datalyr.esm.js +751 -47
  12. package/dist/datalyr.esm.js.map +1 -1
  13. package/dist/datalyr.esm.min.js +1 -1
  14. package/dist/datalyr.esm.min.js.map +1 -1
  15. package/dist/datalyr.js +751 -46
  16. package/dist/datalyr.js.map +1 -1
  17. package/dist/datalyr.min.js +1 -1
  18. package/dist/datalyr.min.js.map +1 -1
  19. package/dist/identify-dedupe.test.d.ts +2 -0
  20. package/dist/identify-dedupe.test.d.ts.map +1 -0
  21. package/dist/identity-pii.test.d.ts +2 -0
  22. package/dist/identity-pii.test.d.ts.map +1 -0
  23. package/dist/identity.d.ts +46 -0
  24. package/dist/identity.d.ts.map +1 -1
  25. package/dist/index.d.ts +68 -0
  26. package/dist/index.d.ts.map +1 -1
  27. package/dist/index.test.d.ts +3 -0
  28. package/dist/index.test.d.ts.map +1 -0
  29. package/dist/queue-403.test.d.ts +2 -0
  30. package/dist/queue-403.test.d.ts.map +1 -0
  31. package/dist/queue.d.ts +17 -0
  32. package/dist/queue.d.ts.map +1 -1
  33. package/dist/storage-migration.test.d.ts +2 -0
  34. package/dist/storage-migration.test.d.ts.map +1 -0
  35. package/dist/storage.d.ts.map +1 -1
  36. package/dist/stripe-session.d.ts +75 -0
  37. package/dist/stripe-session.d.ts.map +1 -0
  38. package/dist/stripe-session.test.d.ts +2 -0
  39. package/dist/stripe-session.test.d.ts.map +1 -0
  40. package/dist/types.d.ts +1 -0
  41. package/dist/types.d.ts.map +1 -1
  42. package/package.json +1 -1
package/README.md CHANGED
@@ -1,28 +1,33 @@
1
1
  # @datalyr/web
2
2
 
3
- Browser SDK for event tracking, user identity, and attribution. Version 1.7.0.
3
+ Browser SDK for event tracking, user identity, and attribution. Version 1.7.8.
4
4
 
5
- ## Table of Contents
5
+ Full reference: [Web SDK](https://docs.datalyr.com/sdk-reference/web).
6
+
7
+ ## Table of contents
6
8
 
7
9
  - [Installation](#installation)
8
- - [Quick Start](#quick-start)
9
- - [How It Works](#how-it-works)
10
+ - [Quick start](#quick-start)
11
+ - [What the SDK tracks without your code](#what-the-sdk-tracks-without-your-code)
12
+ - [Events the SDK sends for you](#events-the-sdk-sends-for-you)
13
+ - [Identity](#identity)
10
14
  - [Configuration](#configuration)
11
- - [API Reference](#api-reference)
12
- - [Event Tracking](#event-tracking)
13
- - [User Identity](#user-identity)
14
- - [Session](#session)
15
- - [Attribution](#attribution)
16
- - [Super Properties](#super-properties)
17
- - [Privacy and Consent](#privacy-and-consent)
18
- - [Queue and Network](#queue-and-network)
19
- - [Container Scripts](#container-scripts)
20
- - [Debugging](#debugging)
21
- - [Lifecycle](#lifecycle)
22
- - [Web-to-App Attribution](#web-to-app-attribution)
23
- - [SPA Support](#spa-support)
24
- - [Framework Integration](#framework-integration)
15
+ - [Remote configuration](#remote-configuration)
16
+ - [Complete method list](#complete-method-list)
17
+ - [Event tracking](#event-tracking)
18
+ - [Attribution](#attribution)
19
+ - [Checkout metadata](#checkout-metadata)
20
+ - [Super properties](#super-properties)
21
+ - [Privacy and consent](#privacy-and-consent)
22
+ - [Queue, network, and limits](#queue-network-and-limits)
23
+ - [Storage](#storage)
24
+ - [Container scripts](#container-scripts)
25
+ - [Debugging](#debugging)
26
+ - [Web-to-app attribution](#web-to-app-attribution)
27
+ - [SPA support](#spa-support)
28
+ - [Framework integration](#framework-integration)
25
29
  - [TypeScript](#typescript)
30
+ - [Verify the install](#verify-the-install)
26
31
  - [Troubleshooting](#troubleshooting)
27
32
  - [License](#license)
28
33
 
@@ -30,419 +35,457 @@ Browser SDK for event tracking, user identity, and attribution. Version 1.7.0.
30
35
 
31
36
  ## Installation
32
37
 
33
- ### NPM
38
+ Choose one method.
34
39
 
35
- ```bash
36
- npm install @datalyr/web
37
- ```
40
+ > **Never load the script tag and the npm package together.** Two instances issue two
41
+ > `visitor_id` values for one person and split the journey.
38
42
 
39
- ### Script Tag
43
+ ### Script tag
40
44
 
41
45
  ```html
42
46
  <script defer src="https://track.datalyr.com/dl.js"
43
47
  data-workspace-id="YOUR_WORKSPACE_ID"></script>
44
48
  ```
45
49
 
46
- The script tag loads externally (zero bundle size) and auto-initializes. Access the SDK via `window.datalyr`.
50
+ The script tag loads externally and initializes on its own. Reach the SDK at
51
+ `window.datalyr`.
52
+
53
+ The script tag reads these attributes:
47
54
 
48
- Choose one installation method. Using both causes conflicts.
55
+ | Attribute | Required | Effect |
56
+ | --- | --- | --- |
57
+ | `data-workspace-id` | Yes | The workspace that receives events. |
58
+ | `data-endpoint` | No | Overrides the ingest URL. |
59
+ | `data-tracked-params` | No | More URL parameters to capture. Comma-separated. |
60
+ | `data-debug` | No | `true` turns on console logging. |
61
+ | `data-auto-identify` | No | `true` or `false`. |
62
+ | `data-platform` | No | `shopify`, `checkoutchamp`, or `generic`. |
63
+ | `data-checkout-champ-domains` | No | Comma-separated domains. |
64
+ | `data-stripe-payment-links` | No | `false` turns off Stripe link decoration. |
65
+ | `data-stripe-link-domains` | No | Comma-separated extra Stripe domains. |
66
+
67
+ ### npm
68
+
69
+ ```bash
70
+ npm install @datalyr/web
71
+ ```
49
72
 
50
- | Feature | Script Tag | NPM |
51
- |---------|-----------|-----|
52
- | Bundle Size | 0 (external) | ~15KB |
53
- | TypeScript | No | Yes |
54
- | Access | `window.datalyr` | `import datalyr` |
73
+ | Property | Script tag | npm |
74
+ | --- | --- | --- |
75
+ | Bundle cost | 0, loaded externally | 88 KB minified, 24 KB gzipped |
76
+ | TypeScript types | No | Yes |
77
+ | Access | `window.datalyr` | `import datalyr from '@datalyr/web'` |
55
78
  | Initialize | Automatic | `datalyr.init()` |
56
79
 
57
80
  ---
58
81
 
59
- ## Quick Start
82
+ ## Quick start
60
83
 
61
84
  ```javascript
62
85
  import datalyr from '@datalyr/web';
63
86
 
64
- datalyr.init({
65
- workspaceId: 'YOUR_WORKSPACE_ID'
66
- });
87
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
67
88
 
68
- // Wait for async initialization (encryption, container, initial page view)
89
+ // Wait for encryption keys, container scripts, and the first page view
69
90
  await datalyr.ready();
70
91
 
71
- // Track events
72
92
  datalyr.track('button_clicked', { button: 'signup' });
73
93
 
74
- // Identify users
75
94
  datalyr.identify('user_123', { email: 'user@example.com' });
76
95
 
77
- // Track page views (page name goes in properties)
78
96
  datalyr.page({ title: 'Pricing', variant: 'A' });
79
97
  ```
80
98
 
99
+ `init()` throws `Error('[Datalyr] workspaceId is required')` when `workspaceId` is
100
+ missing. A second `init()` call logs a warning and returns. Any tracking method called
101
+ before `init()` logs a warning and returns.
102
+
103
+ ---
104
+
105
+ ## What the SDK tracks without your code
106
+
107
+ Two triggers fire on their own.
108
+
109
+ | Trigger | Event name | Turn it off with |
110
+ | --- | --- | --- |
111
+ | First page view after load | `pageview` | `trackPageViews: false` |
112
+ | SPA route change — `pushState`, `replaceState`, `popstate`, `hashchange` | `pageview` | `trackSPA: false` |
113
+
114
+ Nothing else is automatic. The SDK records no clicks, scrolls, form submits, outbound
115
+ links, or errors. There is no session-start event. Session data rides on every event.
116
+ Call `track()` for everything else.
117
+
81
118
  ---
82
119
 
83
- ## How It Works
120
+ ## Events the SDK sends for you
84
121
 
85
- 1. Events are created with `track()`, `page()`, `identify()`, etc.
86
- 2. Each event includes device context, session data, and attribution parameters.
87
- 3. Events are queued locally and sent in batches.
88
- 4. If the browser is offline, events are stored and sent when connectivity returns.
89
- 5. Events are processed server-side for analytics and attribution reporting.
122
+ These names are reserved. Never send one through `track()` yourself.
90
123
 
91
- ### Event Payload
124
+ | Method you call | Event name sent |
125
+ | --- | --- |
126
+ | `identify()` | `$identify` |
127
+ | `group()` | `$group` |
128
+ | `alias()` | `$alias` |
129
+ | `screen()` | `pageview`, with a `screen` property |
130
+ | `trackAppDownloadClick()` | `$app_download_click` |
131
+ | Auto-identify email capture | `$auto_identify`, then `$identify` |
92
132
 
93
- Every event includes:
133
+ ### Event payload
94
134
 
95
135
  ```javascript
96
136
  {
97
137
  event_name: 'purchase',
98
- event_data: { ... }, // Custom + attribution + session properties
138
+ event_data: { /* your properties, plus attribution, session, and context fields */ },
99
139
 
100
140
  // Identity
101
- anonymous_id: 'anon_xxxxx', // Persistent browser ID
102
- distinct_id: 'user_123', // user_id if identified, else anonymous_id
103
- user_id: 'user_123', // Set after identify()
104
- session_id: 'sess_xxxxx', // Current session
141
+ visitor_id: 'anon_3d5cf66d-203f-4009-8bb0-f3714da152a4',
142
+ distinct_id: 'user_123',
143
+ user_id: 'user_123',
144
+ session_id: 'sess_9f2c1b40-1c2e-4d55-9a0b-6a4f7f1d2e33',
105
145
 
106
- // Context (in event_data)
146
+ // Context, inside event_data
107
147
  url: 'https://example.com/pricing',
108
148
  title: 'Pricing',
109
149
  referrer: 'https://google.com',
110
150
 
111
- // Attribution (in event_data, if captured)
151
+ // Attribution, inside event_data
112
152
  utm_source: 'facebook',
113
153
  fbclid: 'abc123',
114
154
 
115
155
  // Metadata
116
156
  workspace_id: 'wk_xxxxx',
117
157
  source: 'web',
118
- timestamp: '2024-01-15T10:30:00Z',
119
- sdk_version: '1.7.0',
158
+ timestamp: '2026-07-25T10:30:00Z',
159
+ sdk_version: '1.7.8',
120
160
  sdk_name: 'datalyr-web-sdk'
121
161
  }
122
162
  ```
123
163
 
124
164
  ---
125
165
 
126
- ## Configuration
127
-
128
- All properties of `DatalyrConfig`:
129
-
130
- ```typescript
131
- datalyr.init({
132
- // Required
133
- workspaceId: string,
134
-
135
- // Endpoint
136
- endpoint?: string, // Default: 'https://ingest.datalyr.com'
137
- fallbackEndpoints?: string[], // Default: [] - Additional endpoints to try on failure
138
-
139
- // Debugging
140
- debug?: boolean, // Default: false - Enable console logging
141
-
142
- // Batching
143
- batchSize?: number, // Default: 10 - Events per batch
144
- flushInterval?: number, // Default: 5000 - Flush interval in ms
145
- flushAt?: number, // Default: 10 - Flush when N events queued
146
-
147
- // Priority events (bypass normal batching)
148
- criticalEvents?: string[], // Default: undefined (disabled). Events flushed immediately.
149
- highPriorityEvents?: string[], // Default: undefined (disabled). Events given queue priority.
150
-
151
- // Session
152
- sessionTimeout?: number, // Default: 3600000 (60 min)
153
- trackSessions?: boolean, // Default: true
154
-
155
- // Attribution
156
- attributionWindow?: number, // Default: 7776000000 (90 days)
157
- trackedParams?: string[], // Default: [] - Additional URL params to capture
158
-
159
- // Privacy
160
- respectDoNotTrack?: boolean, // Default: false - Honor browser DNT header
161
- respectGlobalPrivacyControl?: boolean, // Default: true - Honor GPC signal
162
- privacyMode?: 'standard' | 'strict', // Default: 'standard' - 'strict' limits data collection
163
-
164
- // Cookies / Storage
165
- cookieDomain?: string | 'auto', // Default: 'auto'
166
- cookieExpires?: number, // Default: 365 (days)
167
- secureCookie?: boolean | 'auto', // Default: 'auto'
168
- sameSite?: 'Strict' | 'Lax' | 'None', // Default: 'Lax'
169
- cookiePrefix?: string, // Default: '__dl_'
170
-
171
- // Performance
172
- enablePerformanceTracking?: boolean, // Default: true - Collect navigation timing metrics
173
- enableFingerprinting?: boolean, // Default: true - Minimal device fingerprinting (standard mode only)
174
-
175
- // Retry / Offline
176
- maxRetries?: number, // Default: 5
177
- retryDelay?: number, // Default: 1000 (ms)
178
- maxOfflineQueueSize?: number, // Default: 100
179
-
180
- // SPA
181
- trackSPA?: boolean, // Default: true - Auto-track route changes
182
- trackPageViews?: boolean, // Default: true - Auto-track initial page view
183
-
184
- // Container scripts
185
- enableContainer?: boolean, // Default: true - Load third-party scripts from dashboard
186
-
187
- // Auto-Identify (opt-in)
188
- autoIdentify?: boolean, // Default: false - Automatically identify users
189
- autoIdentifyForms?: boolean, // Default: true - Capture email from forms (when autoIdentify enabled)
190
- autoIdentifyAPI?: boolean, // Default: false - Capture email from API responses (off: same-origin scan can mis-identify; opt in explicitly)
191
- autoIdentifyShopify?: boolean, // Default: true - Capture email from Shopify endpoints (when autoIdentify enabled)
192
- autoIdentifyTrustedDomains?: string[], // Default: [] - Additional domains to trust for API capture
193
-
194
- // Plugins
195
- plugins?: DatalyrPlugin[], // Default: [] - Custom plugin instances
196
- });
197
- ```
166
+ ## Identity
198
167
 
199
- ---
168
+ Four identifiers ship on every event. They are not alternatives to each other.
200
169
 
201
- ## Remote config (Identity Bridge)
170
+ | Wire field | Value |
171
+ | --- | --- |
172
+ | `visitor_id` | The anonymous browser ID, format `anon_<uuid>`. Stays anonymous even after `identify()`. |
173
+ | `user_id` | The ID you pass to `identify()`. `null` until then. |
174
+ | `distinct_id` | The `user_id` when identified. The `visitor_id` before that. |
175
+ | `session_id` | Format `sess_<uuid>`. New after 60 minutes idle. |
202
176
 
203
- These options can be controlled from the dashboard (**Settings Identity & Attribution**) with **no code change** — they're delivered to the SDK at runtime in the `/container-scripts` response and merged in:
177
+ Use `visitor_id` in server-side and webhook work. Stripe, Whop, and Shopify all key off
178
+ it. Use `distinct_id` when you reason about event-level identity.
204
179
 
205
- - `autoIdentify`, `autoIdentifyForms`, `autoIdentifyAPI`, `autoIdentifyShopify`
206
- - `shopifyCartAttributes`
207
- - `checkoutChampDomains`
208
- - `respectGlobalPrivacyControl`, `respectDoNotTrack`, `privacyMode`
180
+ The **Events** and **Conversions** tables label this column `distinct id`. For an
181
+ unidentified visitor it shows the `visitor_id`.
209
182
 
210
- **Precedence, per key:** built-in default ← dashboard ← explicit `init()`. An explicit value in `init()` always wins, so you can pin a value in code and still manage everything else from the dashboard. Changes propagate in ~5 minutes (edge-cached). An older worker that doesn't send the `config` envelope is a no-op — built-in defaults stand.
183
+ ### Identity methods
211
184
 
212
- > `platform` is **not** remote-configurable — set it at install time via the `data-platform` attribute. The CheckoutChamp behaviors run before the remote config loads, so a dashboard-set platform wouldn't drive them.
213
-
214
- ---
185
+ | Method | Returns |
186
+ | --- | --- |
187
+ | `getVisitorId(): string` | The anonymous browser ID. Same value as `getAnonymousId()`. |
188
+ | `getAnonymousId(): string` | The anonymous browser ID. |
189
+ | `getUserId(): string \| null` | The identified user ID, or `null`. |
190
+ | `getDistinctId(): string` | The user ID when identified, otherwise the anonymous browser ID. |
191
+ | `getSessionId(): string` | The current session ID. |
192
+ | `getSessionData(): SessionData \| null` | The full session record. |
193
+ | `startNewSession(): string` | Ends the current session and returns a new session ID. |
194
+ | `getWorkspaceId(): string \| null` | The workspace ID passed to `init()`. |
215
195
 
216
- ## API Reference
217
-
218
- ### Event Tracking
219
-
220
- #### `track(eventName, properties?)`
196
+ ```typescript
197
+ interface SessionData {
198
+ id: string;
199
+ startTime: number;
200
+ lastActivity: number;
201
+ pageViews: number;
202
+ events: number;
203
+ duration: number;
204
+ isActive: boolean;
205
+ }
206
+ ```
221
207
 
222
- Track a custom event.
208
+ ### identify()
223
209
 
224
210
  ```typescript
225
- track(eventName: string, properties?: EventProperties): void
211
+ identify(userId: string, traits?: UserTraits): void
226
212
  ```
227
213
 
228
214
  ```javascript
229
- datalyr.track('signup_started');
230
-
231
- datalyr.track('product_viewed', {
232
- product_id: 'SKU123',
233
- product_name: 'Blue Shirt',
234
- price: 29.99,
235
- currency: 'USD',
236
- category: 'Apparel',
215
+ datalyr.identify('user_123', {
216
+ email: 'user@example.com',
217
+ name: 'John Doe',
218
+ plan: 'premium',
237
219
  });
238
220
  ```
239
221
 
240
- #### `page(properties?)`
241
-
242
- Track a page view. The page `title`, `url`, `path`, `search`, and `referrer` are captured automatically. Pass `properties` to add or override values.
222
+ Call `identify()` after sign-in. Traits are encrypted with AES-GCM before storage.
223
+ Passing a different `user_id` runs `reset()` first, so two people on one browser do not
224
+ merge. `identify()` does not rotate the session ID.
243
225
 
244
- ```typescript
245
- page(properties?: PageProperties): void
246
- ```
226
+ ### reset()
247
227
 
248
228
  ```javascript
249
- // Track current page (all properties auto-captured)
250
- datalyr.page();
251
-
252
- // Override title, add custom properties
253
- datalyr.page({ title: 'Pricing', variant: 'A' });
254
-
255
- // Add custom properties
256
- datalyr.page({ product_id: 'SKU123', source: 'search' });
229
+ datalyr.reset();
257
230
  ```
258
231
 
259
- Note: `page()` does not accept a name string as the first argument. To set the page name, pass it as `title` in the properties object.
232
+ > **Do not call `reset()` on every page load.** It issues a **new** `visitor_id`, which
233
+ > breaks the link between the campaign visit and the later conversion.
260
234
 
261
- #### `screen(screenName, properties?)`
235
+ Call `reset()` on logout. It clears `user_id`, traits, super properties, the captured
236
+ email, the journey, and both touchpoints, then issues a new `visitor_id` and a new
237
+ session. It leaves the Meta `_fbc` and `_fbp` cookies in place.
262
238
 
263
- Track a screen view. Intended for SPAs or hybrid apps.
239
+ ### alias()
264
240
 
265
241
  ```typescript
266
- screen(screenName: string, properties?: Record<string, any>): void
242
+ alias(userId: string, previousId?: string): void
267
243
  ```
268
244
 
269
245
  ```javascript
270
- datalyr.screen('Dashboard');
271
- datalyr.screen('Product Details', { product_id: 'SKU123' });
246
+ datalyr.alias('user_123');
272
247
  ```
273
248
 
274
- Internally fires a `pageview` event with the `screen` property set.
275
-
276
- #### `trackAppDownloadClick(options)`
249
+ > **`previousId` accepts only the current anonymous ID.** Any other value logs
250
+ > `alias() only accepts the current anonymous ID as previousId` and does nothing. Omit the
251
+ > argument to use the current anonymous ID.
277
252
 
278
- Track an app download click, then redirect to the app store. Fires a `$app_download_click` event with full attribution, flushes via `sendBeacon`, and redirects.
253
+ ### group()
279
254
 
280
255
  ```typescript
281
- trackAppDownloadClick(options: {
282
- targetPlatform: 'ios' | 'android';
283
- appStoreUrl: string;
284
- }): void
256
+ group(groupId: string, traits?: Record<string, any>): void
285
257
  ```
286
258
 
287
259
  ```javascript
288
- datalyr.trackAppDownloadClick({
289
- targetPlatform: 'ios',
290
- appStoreUrl: 'https://apps.apple.com/app/your-app/id123456789',
291
- });
260
+ datalyr.group('company_abc', { name: 'Acme Inc', plan: 'enterprise' });
292
261
  ```
293
262
 
294
- For Android Play Store URLs, attribution parameters are automatically encoded into the `referrer` query parameter for deterministic install attribution.
295
-
296
263
  ---
297
264
 
298
- ### User Identity
299
-
300
- #### `identify(userId, traits?)`
265
+ ## Configuration
301
266
 
302
- Link the anonymous visitor to a known user. Rotates the session ID on call (prevents session fixation). User traits are encrypted at rest.
267
+ Only `workspaceId` is required.
303
268
 
304
- ```typescript
305
- identify(userId: string, traits?: UserTraits): void
306
- ```
269
+ > **`sessionTimeout` and `attributionWindow` are milliseconds, not minutes or days.**
270
+ > Setting `sessionTimeout: 60` expires the session after 60 milliseconds and starts a new
271
+ > session on every event.
307
272
 
308
273
  ```javascript
309
- datalyr.identify('user_123', {
310
- email: 'user@example.com',
311
- name: 'John Doe',
312
- plan: 'premium',
313
- company: 'Acme Inc',
274
+ datalyr.init({
275
+ workspaceId: 'YOUR_WORKSPACE_ID',
276
+ sessionTimeout: 3600000, // 60 minutes, in milliseconds
277
+ attributionWindow: 7776000000, // 90 days, in milliseconds
278
+ debug: true,
314
279
  });
315
280
  ```
316
281
 
317
- #### `alias(userId, previousId?)`
318
-
319
- Create an alias linking one user ID to another. If `previousId` is omitted, the current anonymous ID is used.
320
-
321
- ```typescript
322
- alias(userId: string, previousId?: string): void
323
- ```
324
-
325
- ```javascript
326
- datalyr.alias('user_123', 'temp_user_456');
327
- ```
282
+ | Option | Type | Default |
283
+ | --- | --- | --- |
284
+ | `workspaceId` | `string` | Required. Throws when missing. |
285
+ | `endpoint` | `string` | `'https://ingest.datalyr.com'` |
286
+ | `fallbackEndpoints` | `string[]` | `[]`. Tried in order after the primary endpoint fails. |
287
+ | `debug` | `boolean` | `false` |
288
+ | `batchSize` | `number` events | `10`, clamped 1–1000 |
289
+ | `flushInterval` | `number` ms | `5000`, clamped 250–3600000 |
290
+ | `criticalEvents` | `string[]` | `['purchase', 'signup', 'subscribe', 'lead', 'conversion']`. Flushed immediately. |
291
+ | `highPriorityEvents` | `string[]` | `['add_to_cart', 'begin_checkout', 'view_item', 'search']`. Flushed after 1000 ms. |
292
+ | `sessionTimeout` | `number` ms | `3600000` — 60 minutes |
293
+ | `attributionWindow` | `number` ms | `7776000000` — 90 days |
294
+ | `trackedParams` | `string[]` | `[]`, merged onto the default list |
295
+ | `respectDoNotTrack` | `boolean` | `false` |
296
+ | `respectGlobalPrivacyControl` | `boolean` | `true` |
297
+ | `privacyMode` | `'standard' \| 'strict'` | `'standard'` |
298
+ | `cookieDomain` | `string \| 'auto'` | `'auto'` |
299
+ | `cookieExpires` | `number` days | `365` |
300
+ | `secureCookie` | `boolean \| 'auto'` | `'auto'` |
301
+ | `sameSite` | `'Strict' \| 'Lax' \| 'None'` | `'Lax'` |
302
+ | `cookiePrefix` | `string` | `'__dl_'` |
303
+ | `enablePerformanceTracking` | `boolean` | `true` |
304
+ | `enableFingerprinting` | `boolean` | `true`. Off in `strict` privacy mode. |
305
+ | `maxRetries` | `number` | `5`, clamped 0–20 |
306
+ | `retryDelay` | `number` ms | `1000`, clamped 0–60000 |
307
+ | `maxOfflineQueueSize` | `number` events | `100`, clamped 1–100000 |
308
+ | `trackSPA` | `boolean` | `true` |
309
+ | `trackPageViews` | `boolean` | `true` |
310
+ | `enableContainer` | `boolean` | `true`. Set `false` to skip dashboard-configured pixels. |
311
+ | `autoIdentify` | `boolean` | `false`. Forced `false` when `privacyMode` is `'strict'`. |
312
+ | `autoIdentifyForms` | `boolean` | `true`, applied only when `autoIdentify` is `true` |
313
+ | `autoIdentifyAPI` | `boolean` | `false`. A same-origin response scan can mis-identify. |
314
+ | `autoIdentifyShopify` | `boolean` | `true`, applied only when `autoIdentify` is `true` |
315
+ | `autoIdentifyTrustedDomains` | `string[]` | `[]` |
316
+ | `shopifyCartAttributes` | `boolean` | `false`. Becomes `true` when `platform` is `'shopify'`. |
317
+ | `platform` | `'shopify' \| 'checkoutchamp' \| 'generic'` | Unset |
318
+ | `checkoutChampDomains` | `string[]` | Unset |
319
+ | `stripePaymentLinks` | `boolean` | `true` |
320
+ | `stripeLinkDomains` | `string[]` | `[]` |
321
+ | `plugins` | `DatalyrPlugin[]` | `[]` |
322
+
323
+ ### Options that do nothing
324
+
325
+ `DatalyrConfig` declares two options that no code reads. Setting either changes no
326
+ behavior.
327
+
328
+ | Option | Status |
329
+ | --- | --- |
330
+ | `flushAt` | Declared and given a default of `10`. Never read. Use `batchSize`. |
331
+ | `trackSessions` | Declared and given a default of `true`. Never read. Sessions are always tracked. |
328
332
 
329
- #### `group(groupId, traits?)`
333
+ ---
330
334
 
331
- Associate the current user with a group or account.
335
+ ## Remote configuration
332
336
 
333
- ```typescript
334
- group(groupId: string, traits?: Record<string, any>): void
335
- ```
337
+ Datalyr can set these nine options from the dashboard, under **Settings → Identity &
338
+ Attribution**, with no code change. The SDK receives them at runtime in the
339
+ `/container-scripts` response.
336
340
 
337
- ```javascript
338
- datalyr.group('company_abc', { name: 'Acme Inc', plan: 'enterprise' });
339
- ```
341
+ `autoIdentify`, `autoIdentifyForms`, `autoIdentifyAPI`, `autoIdentifyShopify`,
342
+ `shopifyCartAttributes`, `checkoutChampDomains`, `respectGlobalPrivacyControl`,
343
+ `respectDoNotTrack`, `privacyMode`.
340
344
 
341
- #### `reset()`
345
+ Precedence per key: built-in default, then the dashboard value, then the explicit
346
+ `init()` value. A value you pass to `init()` always wins. Changes propagate within about
347
+ 5 minutes because the response is edge-cached.
342
348
 
343
- Clear user identity and start a new session. The anonymous ID is preserved.
349
+ `platform` is not remote-configurable. Set it at install time with the `data-platform`
350
+ attribute. The CheckoutChamp behaviors run before the remote config arrives.
344
351
 
345
- ```javascript
346
- datalyr.reset();
347
- ```
348
-
349
- #### `getAnonymousId()`
352
+ ---
350
353
 
351
- Returns the persistent anonymous ID assigned on first visit.
354
+ ## Complete method list
355
+
356
+ | Method | Signature |
357
+ | --- | --- |
358
+ | `init` | `(config: DatalyrConfig) => void` |
359
+ | `ready` | `() => Promise<void>` |
360
+ | `track` | `(eventName: string, properties?: EventProperties) => void` |
361
+ | `identify` | `(userId: string, traits?: UserTraits) => void` |
362
+ | `page` | `(properties?: PageProperties) => void` |
363
+ | `screen` | `(screenName: string, properties?: Record<string, any>) => void` |
364
+ | `group` | `(groupId: string, traits?: Record<string, any>) => void` |
365
+ | `alias` | `(userId: string, previousId?: string) => void` |
366
+ | `reset` | `() => void` |
367
+ | `flush` | `() => Promise<void>` |
368
+ | `destroy` | `() => void` |
369
+ | `getWorkspaceId` | `() => string \| null` |
370
+ | `getVisitorId` | `() => string` |
371
+ | `getAnonymousId` | `() => string` |
372
+ | `getUserId` | `() => string \| null` |
373
+ | `getDistinctId` | `() => string` |
374
+ | `getSessionId` | `() => string` |
375
+ | `getSessionData` | `() => SessionData \| null` |
376
+ | `startNewSession` | `() => string` |
377
+ | `getAttribution` | `() => Attribution` |
378
+ | `getJourney` | `() => TouchPoint[]` |
379
+ | `setAttribution` | `(attribution: Partial<Attribution>) => void` |
380
+ | `getStripeMetadata` | `() => { client_reference_id: string; metadata: { visitor_id: string } }` |
381
+ | `getWhopCheckoutMetadata` | `() => { visitor_id: string }` |
382
+ | `trackAppDownloadClick` | `(options: { targetPlatform: 'ios' \| 'android'; appStoreUrl: string }) => Promise<void>` |
383
+ | `setConsent` | `(consent: ConsentConfig) => void` |
384
+ | `optOut` | `() => void` |
385
+ | `optIn` | `() => void` |
386
+ | `isOptedOut` | `() => boolean` |
387
+ | `setSuperProperties` | `(properties: Record<string, any>) => void` |
388
+ | `unsetSuperProperty` | `(propertyName: string) => void` |
389
+ | `getSuperProperties` | `() => Record<string, any>` |
390
+ | `getErrors` | `() => ErrorInfo[]` |
391
+ | `getNetworkStatus` | `() => NetworkStatus` |
392
+ | `loadScript` | `(scriptId: string) => void` |
393
+ | `getLoadedScripts` | `() => string[]` |
394
+
395
+ The default export and `window.datalyr` are one shared singleton.
396
+ `createDatalyrInstance()` returns a second independent instance.
397
+
398
+ `trackImmediate()` and `forceFlush()` are not public methods. Names in the bootstrap stub
399
+ that resemble them do not resolve to anything callable. Use `track()` and `flush()`.
352
400
 
353
- ```typescript
354
- getAnonymousId(): string
355
- ```
401
+ ---
356
402
 
357
- #### `getUserId()`
403
+ ## Event tracking
358
404
 
359
- Returns the current user ID set by `identify()`, or `null` if not identified.
405
+ ### track()
360
406
 
361
407
  ```typescript
362
- getUserId(): string | null
408
+ track(eventName: string, properties?: EventProperties): void
363
409
  ```
364
410
 
365
- #### `getDistinctId()`
366
-
367
- Returns the distinct ID. This is `userId` if identified, otherwise `anonymousId`.
411
+ ```javascript
412
+ datalyr.track('signup_started');
368
413
 
369
- ```typescript
370
- getDistinctId(): string
414
+ datalyr.track('product_viewed', {
415
+ product_id: 'SKU123',
416
+ product_name: 'Blue Shirt',
417
+ price: 29.99,
418
+ currency: 'USD',
419
+ category: 'Apparel',
420
+ });
371
421
  ```
372
422
 
373
- ---
374
-
375
- ### Session
376
-
377
- #### `getSessionId()`
378
-
379
- Returns the current session ID.
423
+ ### page()
380
424
 
381
425
  ```typescript
382
- getSessionId(): string
426
+ page(properties?: PageProperties): void
383
427
  ```
384
428
 
385
- #### `startNewSession()`
386
-
387
- Force-start a new session. Returns the new session ID.
388
-
389
- ```typescript
390
- startNewSession(): string
391
- ```
429
+ The SDK captures `title`, `url`, `path`, `search`, and `referrer` on its own. Pass
430
+ `properties` to add or override values.
392
431
 
393
432
  ```javascript
394
- const newSessionId = datalyr.startNewSession();
433
+ datalyr.page();
434
+ datalyr.page({ title: 'Pricing', variant: 'A' });
395
435
  ```
396
436
 
397
- #### `getSessionData()`
437
+ `page()` does not accept a name string as its first argument. Pass the page name as
438
+ `title` inside the properties object.
439
+
440
+ Use `page()` only when automatic tracking is off, or for a second view on one URL.
398
441
 
399
- Returns the current session metadata, or `null` if no active session.
442
+ ### screen()
400
443
 
401
444
  ```typescript
402
- getSessionData(): SessionData | null
445
+ screen(screenName: string, properties?: Record<string, any>): void
403
446
  ```
404
447
 
405
- ```typescript
406
- interface SessionData {
407
- id: string;
408
- startTime: number;
409
- lastActivity: number;
410
- pageViews: number;
411
- events: number;
412
- duration: number;
413
- isActive: boolean;
414
- }
448
+ ```javascript
449
+ datalyr.screen('Dashboard');
450
+ datalyr.screen('Product Details', { product_id: 'SKU123' });
415
451
  ```
416
452
 
417
- #### `ready()`
453
+ `screen()` sends a `pageview` event with the `screen` property set.
418
454
 
419
- Returns a promise that resolves when async initialization is complete (encryption, container loading, initial page view).
455
+ ### ready()
420
456
 
421
457
  ```typescript
422
458
  ready(): Promise<void>
423
459
  ```
424
460
 
461
+ `init()` returns synchronously. Encryption setup, container loading, and the first page
462
+ view finish asynchronously. Await `ready()` when you need all three complete.
463
+
425
464
  ```javascript
426
465
  datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID' });
427
466
  await datalyr.ready();
428
- // Encryption is initialized, container is loaded, safe to track
467
+ datalyr.track('post_init_event');
429
468
  ```
430
469
 
431
470
  ---
432
471
 
433
- ### Attribution
434
-
435
- #### `getAttribution()`
436
-
437
- Returns the current captured attribution data.
472
+ ## Attribution
438
473
 
439
474
  ```typescript
440
475
  getAttribution(): Attribution
476
+ setAttribution(attribution: Partial<Attribution>): void
477
+ getJourney(): TouchPoint[]
441
478
  ```
442
479
 
443
480
  ```javascript
444
481
  const attribution = datalyr.getAttribution();
445
482
  console.log(attribution.source, attribution.medium, attribution.campaign);
483
+
484
+ datalyr.setAttribution({
485
+ source: 'partner',
486
+ medium: 'referral',
487
+ campaign: 'spring_promo',
488
+ });
446
489
  ```
447
490
 
448
491
  ```typescript
@@ -453,51 +496,15 @@ interface Attribution {
453
496
  term?: string | null;
454
497
  content?: string | null;
455
498
  clickId?: string | null;
456
- clickIdType?: string | null; // 'fbclid', 'gclid', 'oppref', etc.
499
+ clickIdType?: string | null; // 'fbclid', 'gclid', 'ttclid', and the rest of the 15 below
457
500
  referrer?: string | null;
458
501
  referrerHost?: string | null;
459
502
  landingPage?: string | null;
460
503
  landingPath?: string | null;
461
504
  timestamp?: number;
462
- [key: string]: any; // Custom tracked params
505
+ [key: string]: any; // Your trackedParams
463
506
  }
464
- ```
465
-
466
- Captured automatically from URLs:
467
-
468
- | Type | Parameters |
469
- |------|------------|
470
- | UTM | `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term` |
471
- | Click IDs | `fbclid`, `gclid`, `ttclid`, `oppref`, `twclid`, `li_fat_id`, `msclkid` |
472
- | Referrer | `referrer`, `landing_page` |
473
-
474
- Attribution supports first-touch (90-day window), last-touch, and journey tracking.
475
-
476
- #### `setAttribution(attribution)`
477
-
478
- Manually set or override attribution data.
479
-
480
- ```typescript
481
- setAttribution(attribution: Partial<Attribution>): void
482
- ```
483
507
 
484
- ```javascript
485
- datalyr.setAttribution({
486
- source: 'partner',
487
- medium: 'referral',
488
- campaign: 'spring_promo',
489
- });
490
- ```
491
-
492
- #### `getJourney()`
493
-
494
- Returns the customer journey as an array of touchpoints (up to 30).
495
-
496
- ```typescript
497
- getJourney(): TouchPoint[]
498
- ```
499
-
500
- ```typescript
501
508
  interface TouchPoint {
502
509
  timestamp: number;
503
510
  source?: string;
@@ -507,75 +514,130 @@ interface TouchPoint {
507
514
  }
508
515
  ```
509
516
 
510
- ---
517
+ First touch and last touch persist for `attributionWindow`, 90 days by default. The
518
+ journey keeps the last 30 touchpoints.
511
519
 
512
- ### Super Properties
520
+ ### Click IDs captured
513
521
 
514
- Super properties are included with every subsequent event.
522
+ The SDK reads these 16 parameters from the URL. Matching is **case-sensitive**.
523
+ They are listed in precedence order, down the left column then down the right.
515
524
 
516
- #### `setSuperProperties(properties)`
525
+ | Parameter | Platform | Parameter | Platform |
526
+ | --- | --- | --- | --- |
527
+ | `fbclid` | Meta | `li_fat_id` | LinkedIn |
528
+ | `gclid` | Google Ads | `sclid` | Snapchat |
529
+ | `gbraid` | Google Ads, iOS | `dclid` | Google Display |
530
+ | `wbraid` | Google Ads, web | `epik` | Pinterest |
531
+ | `ttclid` | TikTok | `rdt_cid` | Reddit |
532
+ | `oppref` | OpenAI Ads | `obclid` | Outbrain |
533
+ | `msclkid` | Microsoft | `irclid` | Impact |
534
+ | `twclid` | X | `ko_click_id` | Klaviyo |
517
535
 
518
- Merge properties into the super properties store.
536
+ Two parameters are normalized to a canonical name:
519
537
 
520
- ```typescript
521
- setSuperProperties(properties: Record<string, any>): void
522
- ```
538
+ | In the URL | Stored as |
539
+ | --- | --- |
540
+ | `ScCid`, `sccid` | `sclid` |
541
+ | `irclickid` | `irclid` |
523
542
 
524
- ```javascript
525
- datalyr.setSuperProperties({ app_version: '2.1.0', environment: 'production' });
526
- ```
543
+ A URL carrying several click IDs sends all of them. `clickIdType` takes the first match in
544
+ the order of the table above. Any click ID forces `medium` to `cpc`.
527
545
 
528
- #### `unsetSuperProperty(propertyName)`
546
+ `oppref` (OpenAI Ads) is captured **as of 1.7.8**. Earlier versions did not, so OpenAI Ads
547
+ web conversions from 1.7.7 and below delivered with `attribution_type: none` unless the
548
+ value was passed by hand. It is ordered after the Google click IDs, matching the iOS and
549
+ React Native SDKs, so it never outranks `fbclid` or `gclid` on a URL carrying both.
529
550
 
530
- Remove a single super property by key.
551
+ ### Campaign parameters captured
531
552
 
532
- ```typescript
533
- unsetSuperProperty(propertyName: string): void
534
- ```
553
+ | Group | Parameters |
554
+ | --- | --- |
555
+ | UTM | `utm_source`, `utm_medium`, `utm_campaign`, `utm_term`, `utm_content` |
556
+ | Always captured | `lyr`, `ref`, `source`, `campaign`, `medium`, `gad_source` |
557
+ | Your `trackedParams` | Added to the rows above, never replacing them |
535
558
 
536
- ```javascript
537
- datalyr.unsetSuperProperty('environment');
538
- ```
559
+ Each UTM is sent twice, under its full name and its stripped alias. `utm_source` also
560
+ arrives as `source`.
539
561
 
540
- #### `getSuperProperties()`
562
+ ### Ad platform cookies read
541
563
 
542
- Returns a copy of the current super properties.
564
+ | Platform | Cookies | Survives a marketing decline |
565
+ | --- | --- | --- |
566
+ | Meta | `_fbp`, `_fbc` | No |
567
+ | Google Ads | `_gcl_aw`, `_gcl_dc`, `_gcl_gb`, `_gcl_ha`, `_gac` | No |
568
+ | Google Analytics | `_ga`, `_gid` | Yes |
569
+ | TikTok | `_ttp`, `_ttc` | No |
570
+ | Snapchat | `_scid` | No |
543
571
 
544
- ```typescript
545
- getSuperProperties(): Record<string, any>
546
- ```
572
+ Campaign parameters survive a marketing decline.
573
+
574
+ The SDK writes `_fbc` and `_fbp` itself when an `fbclid` arrives and Meta has not set
575
+ them. Both last 90 days.
547
576
 
548
577
  ---
549
578
 
550
- ### Privacy and Consent
579
+ ## Checkout metadata
551
580
 
552
- #### `optOut()`
581
+ ```javascript
582
+ const stripe = datalyr.getStripeMetadata();
583
+ // { client_reference_id: 'anon_...', metadata: { visitor_id: 'anon_...' } }
553
584
 
554
- Opt the user out of all tracking. Clears the event queue and persists the preference in a cookie.
585
+ const whop = datalyr.getWhopCheckoutMetadata();
586
+ // { visitor_id: 'anon_...' }
587
+ ```
588
+
589
+ Pass `getStripeMetadata()` into the Stripe object your server creates.
555
590
 
556
591
  ```javascript
557
- datalyr.optOut();
592
+ // Server receives this from the browser, then creates the PaymentIntent
593
+ stripe.paymentIntents.create({
594
+ amount: 4999,
595
+ currency: 'usd',
596
+ metadata: datalyrStripeMetadata.metadata,
597
+ });
558
598
  ```
559
599
 
560
- #### `optIn()`
600
+ The Datalyr Stripe webhook reads `client_reference_id` on a Checkout Session and
601
+ `metadata.visitor_id` on every other Stripe object.
561
602
 
562
- Opt the user back in.
603
+ ### Automatic Stripe link decoration
563
604
 
564
- ```javascript
565
- datalyr.optIn();
566
- ```
605
+ With `stripePaymentLinks: true`, the default, the SDK stamps:
567
606
 
568
- #### `isOptedOut()`
607
+ | Surface | Receives |
608
+ | --- | --- |
609
+ | `<a>` links to `buy.stripe.com`, plus any host in `stripeLinkDomains` | `client_reference_id`, and `prefilled_email` when an email is known |
610
+ | `<stripe-pricing-table>`, `<stripe-buy-button>` | The `client-reference-id` attribute only, never an email |
569
611
 
570
- Returns `true` if the user has opted out.
612
+ The SDK skips `prefilled_email` when `prefilled_email` or `locked_prefilled_email` is
613
+ already present, or when `privacyMode` is `'strict'`. It re-stamps on click and watches
614
+ the DOM for new links, debounced at 150 ms.
615
+
616
+ > **The SDK never decorates `checkout.stripe.com` URLs.** Stripe ignores these parameters
617
+ > on an already-created Checkout Session. The SDK also cannot reach links opened with
618
+ > `window.open()`, links inside an iframe, or links in shadow DOM. Call `getVisitorId()`
619
+ > and pass the value yourself in those cases.
620
+
621
+ ---
622
+
623
+ ## Super properties
624
+
625
+ Super properties attach to every later event. `reset()` clears them.
571
626
 
572
627
  ```typescript
573
- isOptedOut(): boolean
628
+ setSuperProperties(properties: Record<string, any>): void
629
+ unsetSuperProperty(propertyName: string): void
630
+ getSuperProperties(): Record<string, any>
574
631
  ```
575
632
 
576
- #### `setConsent(consent)`
633
+ ```javascript
634
+ datalyr.setSuperProperties({ app_version: '2.1.0', environment: 'production' });
635
+ datalyr.unsetSuperProperty('environment');
636
+ ```
577
637
 
578
- Set granular consent preferences.
638
+ ---
639
+
640
+ ## Privacy and consent
579
641
 
580
642
  ```typescript
581
643
  setConsent(consent: ConsentConfig): void
@@ -586,7 +648,7 @@ datalyr.setConsent({
586
648
  analytics: true,
587
649
  marketing: false,
588
650
  preferences: true,
589
- sale: false // CCPA "Do Not Sell"
651
+ sale: false, // CCPA "Do Not Sell"
590
652
  });
591
653
  ```
592
654
 
@@ -599,42 +661,36 @@ interface ConsentConfig {
599
661
  }
600
662
  ```
601
663
 
602
- #### Privacy Modes
664
+ `setConsent()` is safe to call before `init()`. The SDK persists the choice and applies it
665
+ at initialization.
603
666
 
604
- ```javascript
605
- // Standard mode (default) - normal fingerprinting + tracking
606
- datalyr.init({ workspaceId: '...', privacyMode: 'standard' });
667
+ | Method | Effect |
668
+ | --- | --- |
669
+ | `optOut()` | Stops all tracking. Clears the queue. Writes the `__dl_opt_out` cookie. Stops Stripe link decoration. |
670
+ | `optIn()` | Resumes tracking. |
671
+ | `isOptedOut(): boolean` | The current opt-out state. |
607
672
 
608
- // Strict mode - minimal data collection, no fingerprinting
609
- datalyr.init({ workspaceId: '...', privacyMode: 'strict' });
610
- ```
673
+ `privacyMode: 'strict'` forces `autoIdentify` off, turns off fingerprinting, and stops
674
+ email prefill on Stripe links. The SDK honors Global Privacy Control by default. It
675
+ ignores Do Not Track unless `respectDoNotTrack` is `true`.
611
676
 
612
- The SDK also respects `respectDoNotTrack` and `respectGlobalPrivacyControl` config flags.
677
+ ```javascript
678
+ datalyr.init({ workspaceId: 'YOUR_WORKSPACE_ID', privacyMode: 'strict' });
679
+ ```
613
680
 
614
681
  ---
615
682
 
616
- ### Queue and Network
617
-
618
- #### `flush()`
619
-
620
- Manually flush the event queue. Returns a promise that resolves when all queued events are sent.
683
+ ## Queue, network, and limits
621
684
 
622
685
  ```typescript
623
686
  flush(): Promise<void>
687
+ getNetworkStatus(): NetworkStatus
624
688
  ```
625
689
 
626
690
  ```javascript
627
691
  await datalyr.flush();
628
692
  ```
629
693
 
630
- #### `getNetworkStatus()`
631
-
632
- Returns the current network status as tracked by the SDK.
633
-
634
- ```typescript
635
- getNetworkStatus(): NetworkStatus
636
- ```
637
-
638
694
  ```typescript
639
695
  interface NetworkStatus {
640
696
  isOnline: boolean;
@@ -643,48 +699,75 @@ interface NetworkStatus {
643
699
  }
644
700
  ```
645
701
 
646
- Events are automatically stored when offline and sent when connectivity returns.
702
+ The SDK stores events while the browser is offline and sends them when connectivity
703
+ returns.
647
704
 
648
- ---
705
+ | Limit | Value |
706
+ | --- | --- |
707
+ | Events per batch | 10, configurable to 1000 through `batchSize` |
708
+ | Flush interval | 5000 ms |
709
+ | High-priority flush | 1000 ms |
710
+ | Retries per batch | 5 |
711
+ | Offline queue | 100 events. The oldest is dropped first. |
712
+ | `keepalive` request body | 60000 bytes |
713
+ | `sendBeacon` chunk | 60000 bytes |
714
+ | Duplicate suppression | The last 1000 event hashes |
715
+ | Journey length | 30 touchpoints |
716
+ | Property nesting depth | 5 |
717
+ | Error buffer | 50 |
718
+ | Container script fetch | 3000 ms timeout |
649
719
 
650
- ### Container Scripts
720
+ There is no per-event size limit. Only the transport limits above apply.
651
721
 
652
- Container scripts manage third-party tracking pixels (Meta, Google, TikTok) configured in your Datalyr dashboard.
722
+ ### How failed requests behave
653
723
 
654
- #### `loadScript(scriptId)`
724
+ | Response | Behavior |
725
+ | --- | --- |
726
+ | `429` | The SDK waits for `Retry-After`, default 60 seconds. The batch moves to the offline queue. |
727
+ | `403` | The SDK backs off for 5 minutes and parks the batch in the offline queue. Origin configuration changes, so the batch is not discarded. |
728
+ | Other `4xx`, except `408` | Dropped permanently. Never retried. This is what a wrong `workspaceId` produces. |
729
+ | `5xx`, `408`, network failure | Retried with exponential backoff, up to `maxRetries`, then tried against each `fallbackEndpoints` host. |
655
730
 
656
- Trigger a container script by ID.
731
+ ---
657
732
 
658
- ```typescript
659
- loadScript(scriptId: string): void
660
- ```
733
+ ## Storage
661
734
 
662
- ```javascript
663
- datalyr.loadScript('custom-script-id');
664
- ```
735
+ | Cookie | Value | Lifetime |
736
+ | --- | --- | --- |
737
+ | `__dl_visitor_id` | The `visitor_id` | 365 days |
738
+ | `__dl_opt_out` | `true` or `false` | `cookieExpires` |
739
+
740
+ The SDK writes `__dl_visitor_id` on the root domain with `SameSite=Lax`, and `Secure` on
741
+ HTTPS. A failed root-domain write falls back to a host-only cookie.
742
+
743
+ State lives in `localStorage` under a double `dl_dl_` prefix: `dl_dl_anonymous_id`,
744
+ `dl_dl_user_id`, `dl_dl_journey`, `dl_dl_first_touch`, `dl_dl_last_touch`,
745
+ `dl_dl_offline_queue`. Traits and the captured email are encrypted with AES-GCM.
746
+
747
+ ---
665
748
 
666
- #### `getLoadedScripts()`
749
+ ## Container scripts
667
750
 
668
- Returns an array of loaded script IDs.
751
+ Container scripts are the third-party pixels you configure in the Datalyr dashboard. The
752
+ container is built into `dl.js` and into the npm package. It loads on init. There is no
753
+ separate container snippet to install.
669
754
 
670
755
  ```typescript
756
+ loadScript(scriptId: string): void
671
757
  getLoadedScripts(): string[]
672
758
  ```
673
759
 
674
- The container is built into the `dl.js` tag — it loads your dashboard-configured pixels on init, so there's no separate container script to install:
675
-
676
- ```html
677
- <script defer src="https://track.datalyr.com/dl.js"
678
- data-workspace-id="YOUR_WORKSPACE_ID"></script>
760
+ ```javascript
761
+ datalyr.loadScript('custom-script-id');
762
+ console.log(datalyr.getLoadedScripts());
679
763
  ```
680
764
 
681
- ---
682
-
683
- ### Debugging
765
+ The SDK skips the container when `enableContainer` is `false`, when the visitor opted
766
+ out, when `privacyMode` is `'strict'`, or when marketing consent is declined.
684
767
 
685
- #### `getErrors()`
768
+ ---
686
769
 
687
- Returns an array of SDK errors (up to 50 most recent).
770
+ ## Debugging
688
771
 
689
772
  ```typescript
690
773
  getErrors(): ErrorInfo[]
@@ -704,40 +787,32 @@ interface ErrorInfo {
704
787
  console.log(datalyr.getErrors());
705
788
  ```
706
789
 
707
- Enable `debug: true` in config to log all SDK activity to the console.
708
-
709
- ---
710
-
711
- ### Lifecycle
712
-
713
- #### `destroy()`
790
+ `getErrors()` returns the 50 most recent SDK errors. Set `debug: true` to log all SDK
791
+ activity to the console.
714
792
 
715
- Tear down the SDK instance. Restores patched `history.pushState` / `replaceState`, removes event listeners, cleans up the queue, session, container iframes, auto-identify listeners, and encryption keys. Resets all in-memory state.
793
+ ### destroy()
716
794
 
717
795
  ```typescript
718
796
  destroy(): void
719
797
  ```
720
798
 
721
- ```javascript
722
- datalyr.destroy();
723
- ```
799
+ `destroy()` restores the patched `history.pushState` and `history.replaceState`, removes
800
+ every listener, and clears the queue, the session, container iframes, auto-identify
801
+ listeners, and encryption keys. Use it only when you tear the integration down for good.
724
802
 
725
803
  ---
726
804
 
727
- ## Web-to-App Attribution
728
-
729
- Track users who click a "Download App" button on your website and attribute their app install back to the original web session.
730
-
731
- ### Flow
805
+ ## Web-to-app attribution
732
806
 
733
- 1. User clicks an ad and lands on your page (web SDK captures `fbclid`, UTMs, etc.)
734
- 2. User clicks "Download App" -- `trackAppDownloadClick()` fires with full attribution
735
- 3. User installs from App Store / Play Store
736
- 4. First app open -- mobile SDK recovers the web attribution:
737
- - **Android**: Deterministic match via Play Store `referrer` parameter (~95% accuracy)
738
- - **iOS**: IP-based match against recent web events within 24 hours (~90%+ accuracy)
807
+ ```typescript
808
+ trackAppDownloadClick(options: {
809
+ targetPlatform: 'ios' | 'android';
810
+ appStoreUrl: string;
811
+ }): Promise<void>
812
+ ```
739
813
 
740
- ### Usage
814
+ `trackAppDownloadClick()` fires a `$app_download_click` event with full attribution,
815
+ flushes through `sendBeacon`, then redirects to the store URL.
741
816
 
742
817
  ```javascript
743
818
  document.querySelector('#download-btn').addEventListener('click', () => {
@@ -749,39 +824,45 @@ document.querySelector('#download-btn').addEventListener('click', () => {
749
824
  ```
750
825
 
751
826
  ```javascript
752
- // Android - attribution params auto-encoded into Play Store referrer
753
827
  datalyr.trackAppDownloadClick({
754
828
  targetPlatform: 'android',
755
829
  appStoreUrl: 'https://play.google.com/store/apps/details?id=com.yourapp',
756
830
  });
757
831
  ```
758
832
 
759
- ### Requirements
833
+ For an Android Play Store URL, the SDK encodes the attribution parameters into the
834
+ `referrer` query parameter.
760
835
 
761
- - Web SDK initialized on the prelander page
762
- - Mobile SDK (`@datalyr/react-native` or `@datalyr/swift`) installed in the app
763
- - Attribution is recovered automatically on first app launch
836
+ The mobile SDK recovers the web attribution on first app open:
837
+
838
+ | Platform | Match |
839
+ | --- | --- |
840
+ | Android | Deterministic, through the Play Store `referrer` parameter |
841
+ | iOS | IP-based, against web events from the last 24 hours |
842
+
843
+ This flow needs the Web SDK on the prelander page, and `@datalyr/react-native` or
844
+ `@datalyr/swift` in the app.
764
845
 
765
846
  ---
766
847
 
767
- ## SPA Support
848
+ ## SPA support
768
849
 
769
- Route changes are tracked automatically when `trackSPA: true` (the default). The SDK patches `history.pushState`, `history.replaceState`, and listens for `popstate` and `hashchange` events. Attribution cache is cleared on each navigation so new URL parameters are captured.
850
+ The SDK tracks route changes while `trackSPA` is `true`, the default. It patches
851
+ `history.pushState` and `history.replaceState`, and listens for `popstate` and
852
+ `hashchange`. It clears the attribution cache on each navigation, so new URL parameters
853
+ are captured.
770
854
 
771
855
  ```javascript
772
856
  datalyr.init({
773
857
  workspaceId: 'YOUR_WORKSPACE_ID',
774
- trackSPA: true, // default
775
- trackPageViews: true, // auto-track initial page view, default
858
+ trackSPA: true,
859
+ trackPageViews: true,
776
860
  });
777
-
778
- // Manual page tracking
779
- datalyr.page({ title: 'Product Details', product_id: 'SKU-123' });
780
861
  ```
781
862
 
782
863
  ---
783
864
 
784
- ## Framework Integration
865
+ ## Framework integration
785
866
 
786
867
  ### React
787
868
 
@@ -825,6 +906,9 @@ const trackClick = () => {
825
906
 
826
907
  ### Next.js
827
908
 
909
+ SDK methods run in the browser only. Call them inside `useEffect` or another client-side
910
+ hook.
911
+
828
912
  ```tsx
829
913
  // app/providers.tsx
830
914
  'use client';
@@ -842,7 +926,9 @@ export function AnalyticsProvider({ children }: { children: React.ReactNode }) {
842
926
 
843
927
  return <>{children}</>;
844
928
  }
929
+ ```
845
930
 
931
+ ```tsx
846
932
  // app/layout.tsx
847
933
  import { AnalyticsProvider } from './providers';
848
934
 
@@ -861,8 +947,6 @@ export default function RootLayout({ children }: { children: React.ReactNode })
861
947
 
862
948
  ## TypeScript
863
949
 
864
- All types are exported from the package:
865
-
866
950
  ```typescript
867
951
  import datalyr from '@datalyr/web';
868
952
  import type {
@@ -882,9 +966,7 @@ import type {
882
966
  ErrorInfo,
883
967
  PerformanceMetrics,
884
968
  } from '@datalyr/web';
885
- ```
886
969
 
887
- ```typescript
888
970
  const properties: EventProperties = {
889
971
  product_id: 'SKU-123',
890
972
  price: 99.99,
@@ -900,7 +982,7 @@ const traits: UserTraits = {
900
982
  datalyr.identify('user_123', traits);
901
983
  ```
902
984
 
903
- ### Plugin Interface
985
+ ### Plugin interface
904
986
 
905
987
  ```typescript
906
988
  interface DatalyrPlugin {
@@ -915,56 +997,40 @@ interface DatalyrPlugin {
915
997
 
916
998
  ---
917
999
 
918
- ## Troubleshooting
1000
+ ## Verify the install
919
1001
 
920
- ### Events not appearing
1002
+ 1. Open your published site in a private window.
1003
+ 2. Open **Events** in Datalyr.
1004
+ 3. Confirm a `pageview` arrives with your URL, within 30 seconds.
921
1005
 
922
- 1. Verify `workspaceId` is correct.
923
- 2. Set `debug: true` and check the console.
924
- 3. Confirm `datalyr.getAnonymousId()` returns a value.
925
- 4. Check the Network tab for requests to `ingest.datalyr.com`.
926
- 5. Disable ad blockers or privacy extensions.
1006
+ When nothing arrives, set `debug: true`, then call `getErrors()` and `getNetworkStatus()`.
1007
+
1008
+ ---
1009
+
1010
+ ## Troubleshooting
927
1011
 
928
- ### SDK not initialized
1012
+ | Symptom | Cause | Fix |
1013
+ | --- | --- | --- |
1014
+ | No events, network tab shows a `4xx` | Wrong `workspaceId`. The batch is dropped with no retry. | Copy the ID from **Settings → Tracking → Install** |
1015
+ | No events, no network request | `init()` never ran, or the visitor opted out | Call `datalyr.getWorkspaceId()` and `datalyr.isOptedOut()` |
1016
+ | No events, requests blocked | An ad blocker or a privacy extension | Test in a clean private window |
1017
+ | A session expires on every event | `sessionTimeout` was set in minutes | Set it in milliseconds: `3600000` for 60 minutes |
1018
+ | Attribution disappears after a few days | `attributionWindow` was set in days | Set it in milliseconds: `7776000000` for 90 days |
1019
+ | Two `visitor_id` values for one person | The script tag and the npm package both loaded | Remove one |
1020
+ | Conversions do not link to the visit | `reset()` runs on page load | Call `reset()` on logout only |
1021
+ | A Snapchat or Impact click is not attributed | Uppercase or alias parameter | The SDK maps `ScCid`, `sccid`, and `irclickid` on its own. Confirm the parameter reaches the landing URL. |
1022
+ | Stripe conversions are unattributed | A `checkout.stripe.com` link | Pass `getStripeMetadata()` from your server, or use a `buy.stripe.com` link |
1023
+ | `undefined` from every getter | `init()` never ran | Call `datalyr.init()` first |
929
1024
 
930
1025
  ```javascript
931
- console.log('Anonymous ID:', datalyr.getAnonymousId());
1026
+ console.log('Workspace:', datalyr.getWorkspaceId());
1027
+ console.log('Visitor ID:', datalyr.getVisitorId());
932
1028
  console.log('Session ID:', datalyr.getSessionId());
933
1029
  console.log('User ID:', datalyr.getUserId());
934
1030
  console.log('Network:', datalyr.getNetworkStatus());
935
1031
  console.log('Errors:', datalyr.getErrors());
936
1032
  ```
937
1033
 
938
- If these return `undefined`, the SDK is not initialized. Call `datalyr.init()` first.
939
-
940
- ### Async initialization
941
-
942
- `init()` returns synchronously, but encryption and container loading happen asynchronously. If you need to ensure everything is ready before tracking:
943
-
944
- ```javascript
945
- datalyr.init({ workspaceId: '...' });
946
- await datalyr.ready();
947
- datalyr.track('post_init_event');
948
- ```
949
-
950
- ### Next.js SSR
951
-
952
- SDK methods only work in the browser. Always call them inside `useEffect` or other client-side hooks:
953
-
954
- ```jsx
955
- 'use client';
956
- import { useEffect } from 'react';
957
- import datalyr from '@datalyr/web';
958
-
959
- useEffect(() => {
960
- datalyr.init({ workspaceId: '...' });
961
- }, []);
962
- ```
963
-
964
- ### Script tag vs NPM
965
-
966
- Use one installation method, not both. Using both creates duplicate instances.
967
-
968
1034
  ---
969
1035
 
970
1036
  ## License