> ## Documentation Index
> Fetch the complete documentation index at: https://docs.immutable.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Web SDK

> Typed tracking SDK for websites and web games. Tracks player progressions, spend, sign-ups, and attribution signals

<Warning>
  The Web SDK is currently in **alpha**. APIs and behavior may change between releases.
</Warning>

<Info>
  **Who is this for?** Web developers building web games, marketing sites, or SPAs who need explicit event tracking, user identity, or SPA support. Works with any framework (React, Next.js, Svelte, vanilla JS).
</Info>

The Immutable Web SDK is a typed package for Audience analytics instrumentation. It powers product analytics and [Attribution](/docs/products/attribution/overview): UTM parameters, click IDs, and referrer data are captured automatically. Page views are tracked by calling `page()` on each route change, and in-game moments like progressions, purchases, and sign-ups are triggered by your code. All events flow to the same pipeline as the [Unity SDK](/docs/products/audience/analytics/unity-sdk) and [REST API](/docs/products/audience/analytics/rest-api).

## What You Need

* An [Immutable Hub](https://hub.immutable.com) account with a project
* A publishable key from your project settings ([API keys guide](/docs/guides/advanced-setup/api-keys))

## Installation

<Tabs>
  <Tab title="npm">
    ```bash theme={null} theme={null}
    npm install @imtbl/audience
    ```
  </Tab>

  <Tab title="yarn">
    ```bash theme={null} theme={null}
    yarn add @imtbl/audience
    ```
  </Tab>

  <Tab title="pnpm">
    ```bash theme={null} theme={null}
    pnpm add @imtbl/audience
    ```
  </Tab>

  <Tab title="CDN">
    ```html theme={null} theme={null}
    <script src="https://cdn.immutable.com/audience/v1/imtbl.js"></script>
    ```

    The CDN bundle attaches `ImmutableAudience` to `window` with the same API surface: `init`, `AudienceEvents`, `IdentityType`, `canTrack`, `canIdentify`, and `version`.

    <Info>
      If your site uses a Content Security Policy, see [Content Security Policy](/docs/products/audience/analytics/web-sdk#content-security-policy) for the directives CDN users need to allow.
    </Info>
  </Tab>
</Tabs>

## Quick Start

<Steps>
  <Step title="Initialize the SDK">
    <Tabs>
      <Tab title="npm">
        ```typescript theme={null} theme={null}
        import { Audience, AudienceEvents, IdentityType } from '@imtbl/audience';

        const audience = Audience.init({
          publishableKey: 'YOUR_PUBLISHABLE_KEY',
        });
        ```
      </Tab>

      <Tab title="CDN">
        ```html theme={null} theme={null}
        <script src="https://cdn.immutable.com/audience/v1/imtbl.js"></script>
        <script>
          var audience = ImmutableAudience.init({
            publishableKey: 'YOUR_PUBLISHABLE_KEY',
          });
        </script>
        ```

        Subsequent steps call methods on the `audience` variable. `AudienceEvents` and `IdentityType` are available as `ImmutableAudience.AudienceEvents` and `ImmutableAudience.IdentityType`.
      </Tab>
    </Tabs>

    <Warning>
      Consent defaults to `'none'`. The SDK won't track anything until you explicitly set a consent level.
    </Warning>
  </Step>

  <Step title="Set consent">
    The SDK uses a three-tier consent model (`'none'`, `'anonymous'`, `'full'`). The SDK does not provide a consent UI. Build your own cookie banner or privacy prompt, then call `setConsent` with the appropriate level when the user responds.

    ```typescript theme={null}
    // User accepts anonymous tracking
    audience.setConsent('anonymous');

    // User logs in and accepts full tracking
    audience.setConsent('full');
    ```

    At `'anonymous'`, page views and events are tracked but `identify()` and `alias()` calls are dropped. At `'full'`, all methods are available. See the [Data Dictionary](/docs/products/audience/analytics/data-dictionary#consent-model) for what is collected at each level.
  </Step>

  <Step title="Track page views">
    Call `page()` on each route change. The first call in each session includes [attribution](/docs/products/audience/analytics/data-dictionary#attribution-signals) parameters.

    ```typescript theme={null}
    audience.page();
    ```
  </Step>

  <Step title="Track events">
    Log player actions with `track()`. The SDK ships with [predefined events](/docs/products/audience/analytics/data-dictionary#predefined-events) for common player actions (purchases, sign-ups, progression, wishlist actions, and more) that give you typed properties and autocomplete. You can also pass any custom event string.

    <Tabs>
      <Tab title="npm">
        ```typescript theme={null}
        // Predefined event — typed properties
        audience.track(AudienceEvents.SIGN_UP, { method: 'email' });
        audience.track(AudienceEvents.PURCHASE, { currency: 'USD', value: '9.99' });

        // Custom event — any properties
        audience.track('tutorial_complete', { stepCount: 5 });
        ```
      </Tab>

      <Tab title="CDN">
        ```javascript theme={null}
        // Predefined event — typed properties
        audience.track(ImmutableAudience.AudienceEvents.SIGN_UP, { method: 'email' });
        audience.track(ImmutableAudience.AudienceEvents.PURCHASE, { currency: 'USD', value: '9.99' });

        // Custom event — any properties
        audience.track('tutorial_complete', { stepCount: 5 });
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Identify users">
    Identifies the player by associating their userId with their activity and traits. Call at login or account connection. Requires `'full'` consent.

    <Tabs>
      <Tab title="npm">
        ```typescript theme={null}
        audience.identify('player-123', IdentityType.Email, {
          email: 'user@example.com',
        });
        ```
      </Tab>

      <Tab title="CDN">
        ```javascript theme={null}
        audience.identify('player-123', ImmutableAudience.IdentityType.Email, {
          email: 'user@example.com',
        });
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="Clean up on exit">
    Flushes the queue and shuts down the SDK.

    ```typescript theme={null}
    audience.shutdown();
    ```
  </Step>
</Steps>

### Complete Example

<Tabs>
  <Tab title="npm">
    ```typescript theme={null}
    import { Audience, AudienceEvents, IdentityType } from '@imtbl/audience';

    // 1. Initialize
    const audience = Audience.init({
      publishableKey: 'YOUR_PUBLISHABLE_KEY',
    });

    // 2. Set consent after cookie banner
    audience.setConsent('anonymous');

    // 3. Track page views on route changes
    audience.page();

    // 4. Track player actions
    audience.track(AudienceEvents.PURCHASE, { currency: 'USD', value: '9.99' });

    // 5. Identify after login (requires 'full' consent)
    audience.setConsent('full');
    audience.identify('player-123', IdentityType.Email, { email: 'user@example.com' });

    // 6. Clean up on exit
    audience.shutdown();
    ```
  </Tab>

  <Tab title="CDN">
    ```html theme={null}
    <script src="https://cdn.immutable.com/audience/v1/imtbl.js"></script>
    <script>
      // 1. Initialize
      var audience = ImmutableAudience.init({
        publishableKey: 'YOUR_PUBLISHABLE_KEY',
      });

      // 2. Set consent after cookie banner
      audience.setConsent('anonymous');

      // 3. Track page views on route changes
      audience.page();

      // 4. Track player actions
      audience.track(ImmutableAudience.AudienceEvents.PURCHASE, {
        currency: 'USD',
        value: '9.99',
      });

      // 5. Identify after login (requires 'full' consent)
      audience.setConsent('full');
      audience.identify('player-123', ImmutableAudience.IdentityType.Email, {
        email: 'user@example.com',
      });

      // 6. Clean up on exit
      audience.shutdown();
    </script>
    ```
  </Tab>
</Tabs>

## Auto-Capture

The SDK captures form submissions, outbound link clicks, and scroll depth by default. Internal link clicks and button clicks are off by default, since they can be noisy on interactive pages. Turn them on where the extra signal is useful, such as measuring whether players open a `#trailer` modal or click a call-to-action.

Use the `autocapture` option to control what's collected:

```typescript theme={null}
const audience = Audience.init({
  publishableKey: 'YOUR_PUBLISHABLE_KEY',
  consent: 'anonymous',
  autocapture: {
    internalClicks: true,
    buttons: true,
    forms: false,
  },
});
```

Omit an option to keep its default. Set `forms`, `clicks`, and `scroll` to `false` to disable auto-capture entirely and rely only on your manual `page()` and `track()` calls.

See [`form_submitted`](/docs/products/audience/analytics/data-dictionary#auto-tracked-events) and [`scroll_depth`](/docs/products/audience/analytics/data-dictionary#auto-tracked-events) for exact property shapes. `link_clicked` shares its schema with the [predefined event](/docs/products/audience/analytics/data-dictionary#predefined-events) of the same name: a manual `track('link_clicked', ...)` call for a link auto-capture can't see looks identical on the wire.

## Verify the Integration

Initialize with `debug: true` to see SDK activity in the browser console:

```typescript theme={null}
const audience = Audience.init({
  publishableKey: 'YOUR_PUBLISHABLE_KEY',
  debug: true,
});
```

1. **Console**: Look for log entries showing `page` and any `track()` calls
2. **Network tab**: Look for POST requests to `https://api.immutable.com`. Events flush every 5 seconds or when 20 events accumulate

<Info>
  Remove `debug: true` before deploying to production.
</Info>

## Content Security Policy

If your site enforces a Content-Security-Policy header, add the event endpoint so the SDK can deliver events:

```
connect-src: https://api.immutable.com
```

If you load the SDK from Immutable's CDN, also add the script host:

```
script-src: https://cdn.immutable.com
```

<Info>
  If you install the SDK as a package dependency (npm/yarn/pnpm), only `connect-src` is required. The CDN directive is only needed for the `<script>`-tag installation.
</Info>

For nonce-based CSP (CDN install only), add the `nonce` attribute to your inline `<script>` tag. The nonce covers the inline code only. The CDN-loaded script is authorized by the `script-src` directive above.

```html theme={null}
<script src="https://cdn.immutable.com/audience/v1/imtbl.js"></script>
<script nonce="YOUR_NONCE">
  var audience = ImmutableAudience.init({ publishableKey: 'YOUR_PUBLISHABLE_KEY' });
  audience.setConsent('anonymous');
  audience.page();
</script>
```

## API Reference

<AccordionGroup>
  <Accordion title="Audience.init(config)">
    Creates and returns an `Audience` instance. Call once when your app loads to set up tracking.

    | Parameter                    | Type                              | Required | Default        | Description                                                                                                                                         |
    | ---------------------------- | --------------------------------- | -------- | -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
    | `publishableKey`             | `string`                          | Yes      | —              | API key from [Hub](https://hub.immutable.com)                                                                                                       |
    | `consent`                    | `'none' \| 'anonymous' \| 'full'` | No       | `'none'`       | Initial consent level                                                                                                                               |
    | `debug`                      | `boolean`                         | No       | `false`        | Log SDK activity to the browser console                                                                                                             |
    | `testMode`                   | `boolean`                         | No       | `false`        | When `true`, stamps all events with `test: true` so they can be filtered from production analytics.                                                 |
    | `cookieDomain`               | `string`                          | No       | Current domain | Share cookies across subdomains (e.g. `'.studio.com'`).                                                                                             |
    | `flushInterval`              | `number`                          | No       | `5000`         | How often the queue flushes, in milliseconds                                                                                                        |
    | `flushSize`                  | `number`                          | No       | `20`           | Number of queued messages that triggers a flush                                                                                                     |
    | `baseUrl`                    | `string`                          | No       | Immutable API  | Override the default API base URL                                                                                                                   |
    | `onError`                    | `(err: AudienceError) => void`    | No       | `undefined`    | Callback for failures. `err.code` is one of `'FLUSH_FAILED'`, `'CONSENT_SYNC_FAILED'`, `'NETWORK_ERROR'`, `'VALIDATION_REJECTED'`, `'RATE_LIMITED'` |
    | `autocapture.forms`          | `boolean`                         | No       | `true`         | Auto-capture HTML form submissions                                                                                                                  |
    | `autocapture.clicks`         | `boolean`                         | No       | `true`         | Auto-capture clicks on links to external domains                                                                                                    |
    | `autocapture.internalClicks` | `boolean`                         | No       | `false`        | Auto-capture clicks on same-domain links, including `#hash` links                                                                                   |
    | `autocapture.buttons`        | `boolean`                         | No       | `false`        | Auto-capture button clicks. Form submit buttons are already captured by `form_submitted`, so they're excluded.                                      |
    | `autocapture.scroll`         | `boolean`                         | No       | `true`         | Auto-capture scroll depth milestones                                                                                                                |

    ```typescript theme={null}
    const audience = Audience.init({
      publishableKey: 'YOUR_PUBLISHABLE_KEY',
      consent: 'none',
      debug: true,
      cookieDomain: '.studio.com',
      onError: (err) => console.error(err.code, err.message),
    });
    ```
  </Accordion>

  <Accordion title="setConsent(level)">
    Controls what the SDK is allowed to collect. Call when the user accepts or changes their cookie preferences. Takes effect immediately, no page reload needed. See the [Data Dictionary](/docs/products/audience/analytics/data-dictionary#consent-model) for consent levels and [downgrade behavior](/docs/products/audience/analytics/data-dictionary#downgrading-consent).

    ```typescript theme={null}
    // Upgrade after cookie banner
    audience.setConsent('anonymous');

    // Downgrade — stops tracking, clears cookies
    audience.setConsent('none');
    ```
  </Accordion>

  <Accordion title="canTrack(level)">
    Checks whether a consent level allows tracking. Returns `true` when `level` is `'anonymous'` or `'full'`. Use before calling `track()` or `page()` when you need to guard behavior based on the current consent level, for example when conditionally showing a tracking-dependent UI element.

    | Parameter | Type                              | Description                |
    | --------- | --------------------------------- | -------------------------- |
    | `level`   | `'none' \| 'anonymous' \| 'full'` | The consent level to check |

    ```typescript theme={null}
    import { canTrack } from '@imtbl/audience';

    if (canTrack(currentConsentLevel)) {
      // safe to call track() and page()
    }
    ```
  </Accordion>

  <Accordion title="canIdentify(level)">
    Checks whether a consent level allows player identification. Returns `true` when `level` is `'full'`. Use before calling `identify()` or `alias()` when you need to guard identity-dependent logic, for example to only prompt a player to link accounts when consent allows it.

    | Parameter | Type                              | Description                |
    | --------- | --------------------------------- | -------------------------- |
    | `level`   | `'none' \| 'anonymous' \| 'full'` | The consent level to check |

    ```typescript theme={null}
    import { canIdentify } from '@imtbl/audience';

    if (canIdentify(currentConsentLevel)) {
      // safe to call identify() and alias()
    }
    ```
  </Accordion>

  <Accordion title="page(properties?)">
    Records a page view. Call on every route change to track which pages players visit and in what order. The first call in each session includes [attribution](/docs/products/audience/analytics/data-dictionary#attribution-signals) parameters.

    | Parameter    | Type                      | Description                |
    | ------------ | ------------------------- | -------------------------- |
    | `properties` | `Record<string, unknown>` | Optional custom properties |

    **Requires:** `'anonymous'` or `'full'` consent. Calls at `'none'` are silently dropped.
  </Accordion>

  <Accordion title="track(event, properties?)">
    Records a player action. Call when a player does something you want to measure, such as a purchase, sign-up, level completion, or any custom action. [Predefined events](/docs/products/audience/analytics/data-dictionary#predefined-events) give you typed properties with autocomplete.

    | Parameter    | Type                      | Description                                                                                                       |
    | ------------ | ------------------------- | ----------------------------------------------------------------------------------------------------------------- |
    | `event`      | `string`                  | An `AudienceEvents` value (e.g. `AudienceEvents.PURCHASE`) or any custom string                                   |
    | `properties` | `Record<string, unknown>` | Event properties. Required for predefined events that have required fields (e.g. `purchase`), optional otherwise. |

    **Requires:** `'anonymous'` or `'full'` consent. Calls at `'none'` are silently dropped.

    ```typescript theme={null}
    // Predefined event — typed properties
    audience.track(AudienceEvents.PURCHASE, { currency: 'USD', value: '9.99', item_name: 'Sword' });

    // Custom event — any properties
    audience.track('tutorial_complete', { stepCount: 5 });
    ```
  </Accordion>

  <Accordion title="identify(id, identityType, traits?)">
    Associates the player's userId with their activity and traits. Call at login or account connection.

    | Parameter      | Type           | Description                                          |
    | -------------- | -------------- | ---------------------------------------------------- |
    | `id`           | `string`       | The player's identifier in that provider             |
    | `identityType` | `IdentityType` | Which provider the ID comes from (see table below)   |
    | `traits`       | `object`       | Optional. `email`, `name`, or custom key-value pairs |

    **Requires:** `'full'` consent.

    ```typescript theme={null}
    audience.identify('player-456', IdentityType.Email, {
      email: 'user@example.com',
    });
    ```

    **Identity types:**

    | Value        | Provider               |
    | ------------ | ---------------------- |
    | `'passport'` | Passport               |
    | `'steam'`    | Steam                  |
    | `'epic'`     | Epic Games             |
    | `'google'`   | Google                 |
    | `'apple'`    | Apple                  |
    | `'discord'`  | Discord                |
    | `'email'`    | Email address          |
    | `'custom'`   | Custom identity system |
  </Accordion>

  <Accordion title="alias(from, to)">
    Links two account IDs that belong to the same player. Call when a player connects a second account with a different provider, for example a player previously identified via Steam who later links an email account.

    | Parameter | Type                                         | Description               |
    | --------- | -------------------------------------------- | ------------------------- |
    | `from`    | `{ id: string, identityType: IdentityType }` | The account being linked  |
    | `to`      | `{ id: string, identityType: IdentityType }` | The player's main account |

    **Requires:** `'full'` consent.

    ```typescript theme={null}
    audience.alias(
      { id: 'steam-user-789', identityType: IdentityType.Steam },
      { id: 'player-456', identityType: IdentityType.Email },
    );
    ```
  </Accordion>

  <Accordion title="getAnonymousId()">
    Returns the anonymous ID for the current browser session. Useful for debugging, passing the ID to a third-party tool, or carrying it across a browser redirect. See [Cross-Browser Tracking](#cross-browser-tracking).

    ```typescript theme={null}
    const aid = audience.getAnonymousId();
    ```
  </Accordion>

  <Accordion title="reset()">
    Wipes the current player's identity and starts a fresh anonymous session. Call when a player logs out so the next player on the same device isn't mixed up. Reset does not change the consent level, so studios using an explicit consent prompt should also call `setConsent('none')` at logout if the next player should start unconsented.

    ```typescript theme={null}
    audience.reset();
    ```
  </Accordion>

  <Accordion title="flush()">
    Sends all queued events to the server immediately. Call when you need events delivered right now instead of waiting for the next automatic flush. Returns a `Promise` that resolves when the batch is sent.

    ```typescript theme={null}
    await audience.flush();
    ```
  </Accordion>

  <Accordion title="shutdown()">
    Sends any remaining events and shuts down the SDK. Call when the app unmounts or the page is about to unload.
  </Accordion>
</AccordionGroup>

## Cross-Browser Tracking

When a player moves between domains or browser contexts, the SDK mints a fresh anonymous ID in the new context and their prior session history is disconnected.

To carry the anonymous ID across, append it to the URL:

```typescript theme={null}
const url = new URL(destination);
url.searchParams.set('imtbl_aid', audience.getAnonymousId());
window.open(url.toString(), '_blank');
// e.g. https://yoursite.com/?imtbl_aid=a1b2c3d4-...
```

The SDK reads `imtbl_aid` automatically on init, adopts it as the anonymous ID for that session, and removes the parameter from the URL. No extra code is needed on the landing page.

If the player logs in after the redirect, call `identify()` as normal. The anonymous session is linked to their account.

<Info>
  This works for any redirect where you control the destination URL. It does not apply when the OS or browser handles navigation directly (for example, a Share sheet or a bookmark), since there is no opportunity to append parameters before the URL is opened.
</Info>

## Event Delivery

* Events are batched and flushed every **5 seconds** or when **20 events** accumulate (configurable via `flushInterval` and `flushSize`)
* On page unload (navigate away, close tab), the queue flushes remaining events so they are not lost

## FAQ

<AccordionGroup>
  <Accordion title="What happens if I call track() before consent is set?">
    The call is a no-op. No events are queued or sent. Once you call `setConsent('anonymous')` or higher, subsequent calls will be tracked.
  </Accordion>

  <Accordion title="What browsers are supported?">
    Chrome 80+, Firefox 78+, Safari 14+, Edge 80+.
  </Accordion>

  <Accordion title="How do I handle a GDPR erasure request?">
    Erasure is server-side only: call `DELETE /v1/audience/data` from your backend with the player's `userId` or `anonymousId`, authenticated with your secret API key. There is no client-side method for this, since a publishable key is public and can't prove ownership of the data being erased. See [Deleting User Data](/docs/products/audience/analytics/rest-api#deleting-user-data) in the REST API docs for the request shape.

    After your backend confirms the erasure request was accepted, call `reset()` on the client so the next session isn't linked to the erased player:

    ```typescript theme={null}
    audience.reset();
    ```
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Attribution" icon="route" href="/docs/products/attribution/overview">
    How tracking data powers player attribution and Hub reports
  </Card>

  <Card title="Data Dictionary" icon="book" href="/docs/products/audience/analytics/data-dictionary">
    Full reference of event schemas and consent levels
  </Card>

  <Card title="Unity SDK" icon="unity" href="/docs/products/audience/analytics/unity-sdk">
    In-game tracking for Unity (desktop and mobile)
  </Card>

  <Card title="REST API" icon="server" href="/docs/products/audience/analytics/rest-api">
    Send events from your backend or game server
  </Card>

  <Card title="Conversion Postbacks" icon="share-from-square" href="/docs/products/attribution/postbacks/overview">
    Send attributed conversions back to ad networks to improve campaign optimisation
  </Card>

  <Card title="API Keys" icon="key" href="/docs/guides/advanced-setup/api-keys">
    Manage your publishable and secret keys
  </Card>
</CardGroup>
