> ## 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.

# Unity SDK

> Typed tracking SDK for Unity games on PC and mobile. Captures session lifecycle, progressions, purchases, and identity signals

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

<Info>
  **Who is this for?** Unity engineers building games on **Windows, macOS, Linux, iOS, or Android** who want typed in-game tracking, identity, and offline-safe queuing.
</Info>

The Immutable Unity SDK instruments the in-game layer of the player journey as part of the Immutable attribution system, connecting the campaigns and referral links that drove players to your game with their actual in-game behavior. Session lifecycle and a launch event are captured automatically. In-game moments like progressions, purchases, and milestones are triggered by your code.

## What You Need

* An [Immutable Hub](https://hub.immutable.com) account with a project ([get started here](/docs/products/hub/getting-started))
* A publishable key from your project settings ([API keys guide](/docs/products/hub/api-keys))
* A target build platform. Tested on:
  * **Windows 10+**
  * **macOS Sequoia 15.7.4+**
  * **Linux Ubuntu 22.04 LTS+**
  * **iOS 13+**
  * **Android 5.0+ (API level 21+)**
* A Unity project on a supported version:
  * [Unity 2021.3.45f2+](unityhub://2021.3.45f2/88f88f591b2e)
  * [Unity 2022.3.62f2+](unityhub://2022.3.62f2/7670c08855a9)
  * [Unity 6000.4.0f1+](unityhub://6000.4.0f1/8cf496087c8f)
* A Unity project using **Mono** or **IL2CPP** as the scripting backend (both are fully supported)

Mobile builds targeting iOS or Android with attribution signals require additional setup. See [Mobile](#mobile).

## Installation

Install the package via Unity Package Manager using a Git URL.

1. In Unity, open **Window → Package Manager**.
2. Click the **+** button in the top-left, then **Add package from git URL...**.
3. Paste the URL below and click **Add**:

```
https://github.com/immutable/unity-immutable-sdk.git?path=src/Packages/Audience#audience/v0.2.2
```

The package appears in your project as **Immutable Audience** under **Packages** in the Project window.

To update to a newer release, edit the version tag in `Packages/manifest.json` directly.

## Quick Start

<Info>
  Prefer learning from a running project? A working Unity sample lives at [unity-immutable-sdk/examples/audience](https://github.com/immutable/unity-immutable-sdk/tree/main/examples/audience). Clone the repository, set your publishable key, and run.
</Info>

<Steps>
  <Step title="Initialize the SDK at boot">
    Call `ImmutableAudience.Init` once at game launch. You can mark a static method with `[RuntimeInitializeOnLoadMethod]` to have Unity call it before any scene loads.

    ```csharp theme={null}
    using Immutable.Audience;
    using UnityEngine;

    public static class Analytics
    {
        [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
        private static void Init()
        {
            ImmutableAudience.Init(new AudienceConfig
            {
                PublishableKey = "YOUR_PUBLISHABLE_KEY",
                Consent = ConsentLevel.Anonymous,
                DistributionPlatform = DistributionPlatforms.Steam,
                Debug = true,
            });
        }
    }
    ```

    <Warning>
      Consent defaults to `ConsentLevel.None`, which suppresses all tracking. The example above sets `Anonymous` directly so events flow immediately. For an in-game privacy prompt, see the next step.
    </Warning>
  </Step>

  <Step title="Set consent">
    The SDK uses a three-tier consent model (`None`, `Anonymous`, `Full`). The default is `None`. The SDK does not collect anything until you explicitly raise it. The SDK does not provide a consent UI. Build your own in-game prompt and call `SetConsent` when the player responds.

    ```csharp theme={null}
    // Player accepts anonymous tracking
    ImmutableAudience.SetConsent(ConsentLevel.Anonymous);

    // Player logs in and accepts full tracking
    ImmutableAudience.SetConsent(ConsentLevel.Full);

    // Player revokes consent (clears identity, purges queued events, stops collection)
    ImmutableAudience.SetConsent(ConsentLevel.None);
    ```

    `SetConsent` takes effect immediately. Lowering the level purges queued events the new level no longer permits. See the [Data Dictionary](/docs/products/audience/data-dictionary#consent-model) for what each level collects.
  </Step>

  <Step title="Track events">
    Use `ImmutableAudience.Track` to log a player action. The SDK ships with [predefined events](/docs/products/audience/data-dictionary#predefined-events) for common player actions and accepts any custom event string.

    ```csharp theme={null}
    // Predefined event with typed properties
    ImmutableAudience.Track(new Purchase
    {
        Currency = "USD",
        Value = 9.99m,
        ItemName = "Sword of Truth",
    });

    // Custom event with any properties
    ImmutableAudience.Track("tutorial_complete", new Dictionary<string, object>
    {
        ["stepCount"] = 5,
    });
    ```
  </Step>

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

    ```csharp theme={null}
    ImmutableAudience.Identify(
        userId: "76561190000000000",
        identityType: IdentityType.Steam,
        traits: new Dictionary<string, object>
        {
            ["personaName"] = "Player1",
        });
    ```
  </Step>

  <Step title="Clean up on exit">
    Called automatically on `Application.quitting`. For manual teardown:

    ```csharp theme={null}
    // Manual teardown: flush pending events, then stop the SDK.
    await ImmutableAudience.FlushAsync();
    ImmutableAudience.Shutdown();
    ```
  </Step>
</Steps>

### Complete Example

```csharp theme={null}
using System.Collections.Generic;
using Immutable.Audience;
using UnityEngine;

public static class Analytics
{
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)]
    private static async void Init()
    {
        // 1. Initialize
        ImmutableAudience.Init(new AudienceConfig
        {
            PublishableKey = "YOUR_PUBLISHABLE_KEY",
        });

        // 2. Set consent after privacy prompt
        ImmutableAudience.SetConsent(ConsentLevel.Anonymous);

        // 3. Track player actions
        ImmutableAudience.Track(new Purchase { Currency = "USD", Value = 9.99m });

        // 4. Identify after login (requires Full consent)
        ImmutableAudience.SetConsent(ConsentLevel.Full);
        ImmutableAudience.Identify("76561190000000000", IdentityType.Steam, new Dictionary<string, object>
        {
            ["personaName"] = "Player1",
        });

        // 5. Clean up on exit (auto-called on Application.quitting)
        await ImmutableAudience.FlushAsync();
        ImmutableAudience.Shutdown();
    }
}
```

## Verify the Integration

After Init, the SDK exposes its state through read-only properties on `ImmutableAudience`. Log them to confirm a healthy install:

```csharp theme={null}
Debug.Log($"Initialized: {ImmutableAudience.Initialized}");
Debug.Log($"AnonymousId: {ImmutableAudience.AnonymousId}");
Debug.Log($"SessionId: {ImmutableAudience.SessionId}");
Debug.Log($"QueueSize: {ImmutableAudience.QueueSize}");
```

Pair with an `OnError` callback in your `AudienceConfig` to surface failures (see [Error Handling](#error-handling)):

```csharp theme={null}
OnError = err => Debug.LogWarning($"{err.Code}: {err.Message}"),
```

A healthy install:

| Property      | Healthy value                                                                                               |
| ------------- | ----------------------------------------------------------------------------------------------------------- |
| `Initialized` | `true`                                                                                                      |
| `AnonymousId` | non-null GUID                                                                                               |
| `SessionId`   | non-null GUID once [`session_start`](/docs/products/audience/data-dictionary#auto-tracked-events) has fired |
| `QueueSize`   | `1` immediately after a `Track` call, then `0` once flushed                                                 |
| Unity Console | no SDK warnings, no `OnError` invocations                                                                   |

## Mobile

Mobile adds opt-in device-level attribution signals for iOS and Android.

### Attribution opt-in

Two gates must both be set before any mobile attribution data ships:

| Gate                                         | Where                                                                                                                     |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Build-time** `AUDIENCE_MOBILE_ATTRIBUTION` | **Player Settings → Other Settings → Scripting Define Symbols**. Set separately for each player target (iOS and Android). |
| **Runtime** `EnableMobileAttribution = true` | [`AudienceConfig` passed to `Init`](#initaudienceconfig). Controls whether attribution data is collected at runtime.      |

Setting `AUDIENCE_MOBILE_ATTRIBUTION` does three things: adds the `AD_ID` manifest permission on Android, compiles in native attribution code, and switches the iOS privacy manifest to the tracking variant (`NSPrivacyTracking = true`).

If neither gate is set, the build ships without attribution code: no `AD_ID` manifest permission, no native tracking, and `NSPrivacyTracking = false` in the iOS privacy manifest.

### Platform setup

<Tabs>
  <Tab title="iOS">
    **1. Create a Mobile Build Settings asset**

    The iOS build post-processors need a `NSUserTrackingUsageDescription` string. Apple rejects builds that omit this key. Create the asset once per Unity project:

    1. **Assets → Create → Immutable Audience → Mobile Build Settings**
    2. Set **Tracking Usage Description** to a specific description of what you collect and why. Apple rejects generic copy.

    **2. Request [App Tracking Transparency (ATT)](https://developer.apple.com/documentation/apptrackingtransparency) authorization**

    Call `RequestTrackingAuthorizationAsync` at a contextually appropriate moment. The IDFA, when authorized, ships automatically in the next [`game_launch`](/docs/products/audience/data-dictionary#auto-tracked-events). A `tracking_authorization_changed` event fires on any subsequent status transition.

    ATT requires iOS 14+. On iOS 13 the call resolves to `NotDetermined` and IDFA is unavailable. All other SDK features work normally.

    ```csharp theme={null}
    var status = await ImmutableAudience.RequestTrackingAuthorizationAsync();
    ```

    **[Privacy manifest](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files) (automatic)**

    No action required. The SDK ships a `PrivacyInfo.xcprivacy` that Unity merges into the generated Xcode project. The post-processor selects the correct variant based on the `AUDIENCE_MOBILE_ATTRIBUTION` define:

    | Build                         | `NSPrivacyTracking` | Declared data types                                       |
    | ----------------------------- | ------------------- | --------------------------------------------------------- |
    | Default                       | `false`             | IDFV (`NSPrivacyCollectedDataTypeDeviceID`)               |
    | `AUDIENCE_MOBILE_ATTRIBUTION` | `true`              | IDFV + IDFA (`NSPrivacyCollectedDataTypeAdvertisingData`) |

    No Required Reason APIs are used by the SDK. `NSPrivacyAccessedAPITypes` in the manifest reflects Unity engine internals only.
  </Tab>

  <Tab title="Android">
    **1. Add the Google Advertising ID (GAID) dependency**

    GAID requires the Play Services Ads Identifier dependency. Without it, a `ClassNotFoundException` is logged on every launch when `AUDIENCE_MOBILE_ATTRIBUTION` is set. Enable **Custom Main Gradle Template** under **Player Settings → Publishing Settings**, then add to `Assets/Plugins/Android/mainTemplate.gradle`. The `**DEPS**` token is a Unity placeholder expanded at build time. Do not remove it:

    ```gradle theme={null}
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
    **DEPS**
        implementation 'com.google.android.gms:play-services-ads-identifier:18.0.1'
    }
    ```

    The SDK fetches GAID on a background thread (required by Google).

    <Info>
      GAID is unavailable on first launch. The first [`game_launch`](/docs/products/audience/data-dictionary#auto-tracked-events) event will not include `gaid` or `gaidLimitAdTracking`. Both are available from the second launch onwards.
    </Info>

    **[Play Install Referrer](https://developer.android.com/google/play/installreferrer) (automatic)**

    No setup required. The referrer string ships as a dedicated [`install_referrer_received`](/docs/products/audience/data-dictionary#auto-tracked-events) event fired once per install.
  </Tab>
</Tabs>

## API Reference

All methods are static on `Immutable.Audience.ImmutableAudience` and are safe to call from any thread after `Init` returns.

<AccordionGroup>
  <Accordion title="Init(AudienceConfig)">
    Starts the SDK. Call once at game launch (see the [Quick Start](#quick-start)). Subsequent calls are ignored with a warning.

    Throws `ArgumentNullException` if `config` is null, and `ArgumentException` if `PublishableKey` is empty.

    ```csharp theme={null}
    ImmutableAudience.Init(new AudienceConfig
    {
        PublishableKey = "YOUR_PUBLISHABLE_KEY",
        Consent = ConsentLevel.None,
        DistributionPlatform = DistributionPlatforms.Steam,
        Debug = true,
        OnError = err => Debug.LogWarning($"{err.Code}: {err.Message}"),
    });
    ```

    **`AudienceConfig` fields:**

    | Field                     | Type                     | Required | Default             | Description                                                                                                                                                                                                                                                                                                                                            |
    | ------------------------- | ------------------------ | -------- | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `PublishableKey`          | `string`                 | Yes      | (none)              | API key from [Hub](https://hub.immutable.com).                                                                                                                                                                                                                                                                                                         |
    | `Consent`                 | `ConsentLevel`           | No       | `None`              | Initial consent level. The SDK collects nothing until this is `Anonymous` or higher.                                                                                                                                                                                                                                                                   |
    | `DistributionPlatform`    | `string?`                | No       | null                | The platform the build is shipping on. `Init` normalizes this to lowercase, so case at the call site does not matter. Use `DistributionPlatforms.*` constants for the standard values ([see below](#distribution-platforms)).                                                                                                                          |
    | `Debug`                   | `bool`                   | No       | `false`             | Enable SDK warnings via `UnityEngine.Debug.Log`. Disable in release builds.                                                                                                                                                                                                                                                                            |
    | `TestMode`                | `bool`                   | No       | `false`             | Mark all events sent in this session as test traffic. Use during development or QA to separate test events from production data in analytics.                                                                                                                                                                                                          |
    | `FlushIntervalSeconds`    | `int`                    | No       | `5`                 | How often pending events are flushed to the backend.                                                                                                                                                                                                                                                                                                   |
    | `FlushSize`               | `int`                    | No       | `20`                | Flush as soon as this many events are queued.                                                                                                                                                                                                                                                                                                          |
    | `OnError`                 | `Action<AudienceError>?` | No       | null                | Callback fired on errors. Runs on a background thread. Marshal to the main thread before touching Unity APIs. `AudienceError.Code` is one of `FlushFailed`, `ValidationRejected`, `ConsentSyncFailed`, `NetworkError`, `ConsentPersistFailed` (see [Error Handling](#error-handling) for descriptions).                                                |
    | `PersistentDataPath`      | `string?`                | No       | (auto from Unity)   | Directory for the SDK identity, consent, and queued-event files. The Unity integration fills this in from `Application.persistentDataPath`.                                                                                                                                                                                                            |
    | `PackageVersion`          | `string`                 | No       | SDK package version | Library version sent on every message. Override only if you need to report a different version (e.g. wrapping the SDK in your own package).                                                                                                                                                                                                            |
    | `ShutdownFlushTimeoutMs`  | `int`                    | No       | `2000`              | Maximum time `Shutdown` waits for the final flush.                                                                                                                                                                                                                                                                                                     |
    | `EnableMobileAttribution` | `bool`                   | No       | `false`             | Opts into mobile attribution signals ([ATT](https://developer.apple.com/documentation/apptrackingtransparency)/IDFA/[SKAdNetwork](https://developer.apple.com/documentation/storekit/skadnetwork) on iOS, GAID/Install Referrer on Android). Both this flag and the `AUDIENCE_MOBILE_ATTRIBUTION` scripting define must be set. See [Mobile](#mobile). |

    #### Distribution platforms

    `DistributionPlatform` is a free-form string sent in the `distributionPlatform` property of the [`game_launch`](/docs/products/audience/data-dictionary#auto-tracked-events) event. `Init` lowercases the value before it goes on the wire. Use the `DistributionPlatforms` constants when one matches:

    | Constant                           | Value          |
    | ---------------------------------- | -------------- |
    | `DistributionPlatforms.Steam`      | `"steam"`      |
    | `DistributionPlatforms.Epic`       | `"epic"`       |
    | `DistributionPlatforms.GOG`        | `"gog"`        |
    | `DistributionPlatforms.Itch`       | `"itch"`       |
    | `DistributionPlatforms.Standalone` | `"standalone"` |

    If you ship on a platform not in this list, pass the lowercase short name as a string.
  </Accordion>

  <Accordion title="SetConsent(ConsentLevel level)">
    Changes the consent level. Pass `ConsentLevel.None`, `ConsentLevel.Anonymous`, or `ConsentLevel.Full`. Takes effect immediately. Lowering the level purges queued events that the new level no longer permits. Lowering to `None` also clears the anonymous ID.

    ```csharp theme={null}
    ImmutableAudience.SetConsent(ConsentLevel.Full);
    ```

    The new level is persisted to disk so it survives the next launch. The SDK also notifies the backend asynchronously for audit logging. Failures fire `OnError` with `AudienceErrorCode.ConsentSyncFailed` but do not affect local state.
  </Accordion>

  <Accordion title="Track(IEvent)">
    Sends a predefined event. See the [Data Dictionary](/docs/products/audience/data-dictionary#predefined-events) for available event classes and their schemas.

    | Parameter | Type     | Description                                                           |
    | --------- | -------- | --------------------------------------------------------------------- |
    | `evt`     | `IEvent` | The typed event instance. Null events are dropped with a log warning. |

    **Requires:** `Anonymous` or `Full` consent. Calls at `None` are silently dropped.

    ```csharp theme={null}
    ImmutableAudience.Track(new Purchase { Currency = "USD", Value = 9.99m });
    ```
  </Accordion>

  <Accordion title="Track(string eventName, Dictionary<string, object>? properties)">
    Sends a custom event with arbitrary properties. No validation runs on this overload. Prefer [`Track(IEvent)`](#trackievent) for predefined events. Property values can be any JSON-serializable primitive or string. Strings are truncated at 256 characters.

    | Parameter    | Type                          | Description                                                                                      |
    | ------------ | ----------------------------- | ------------------------------------------------------------------------------------------------ |
    | `eventName`  | `string`                      | Required, non-empty. The custom event name. Null or empty values are dropped with a log warning. |
    | `properties` | `Dictionary<string, object>?` | Optional. Event properties. Defaults to `null`.                                                  |

    **Requires:** `Anonymous` or `Full` consent.

    ```csharp theme={null}
    ImmutableAudience.Track("tutorial_complete", new() { ["stepCount"] = 5 });
    ```
  </Accordion>

  <Accordion title="Identify(string userId, IdentityType identityType, Dictionary? traits = null)">
    Associates the player's userId with their in-game activity and traits. Call at login or account connection. Use `IdentityType.Custom` for providers not in the enum.

    | Parameter      | Type                          | Description                                                                                                                                                                                                                                                  |
    | -------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
    | `userId`       | `string`                      | Required, non-empty. The player's identifier in the chosen provider. The SDK silently drops calls with null or empty `userId` (a `Debug.LogWarning` is emitted) so a missing ID does not crash the game. Gate calls on a populated value during development. |
    | `identityType` | `IdentityType`                | Required. Which provider the ID comes from (see table below).                                                                                                                                                                                                |
    | `traits`       | `Dictionary<string, object>?` | Optional. Custom user traits (e.g. `personaName`). Defaults to `null`.                                                                                                                                                                                       |

    **Requires:** `Full` consent. Calls at lower levels are dropped with a log warning.

    ```csharp theme={null}
    ImmutableAudience.Identify("76561190000000000", IdentityType.Steam, new() { ["personaName"] = "Player1" });
    ```

    **Identity types:**

    | Value                   | Provider               |
    | ----------------------- | ---------------------- |
    | `IdentityType.Passport` | Immutable Passport     |
    | `IdentityType.Steam`    | Steam                  |
    | `IdentityType.Epic`     | Epic Games             |
    | `IdentityType.Google`   | Google                 |
    | `IdentityType.Apple`    | Apple                  |
    | `IdentityType.Discord`  | Discord                |
    | `IdentityType.Email`    | Email address          |
    | `IdentityType.Custom`   | Custom identity system |
  </Accordion>

  <Accordion title="Alias(string fromId, IdentityType fromType, string toId, IdentityType toType)">
    Links two user IDs that belong to the same player. Call when a player connects a second account (e.g. a player with an internal game account ID later signs in via Steam).

    | Parameter  | Type           | Description                                     |
    | ---------- | -------------- | ----------------------------------------------- |
    | `fromId`   | `string`       | Required, non-empty. The account being linked.  |
    | `fromType` | `IdentityType` | Required. The provider for `fromId`.            |
    | `toId`     | `string`       | Required, non-empty. The player's main account. |
    | `toType`   | `IdentityType` | Required. The provider for `toId`.              |

    **Requires:** `Full` consent. Both `fromType` and `toType` are required for data-deletion matching.

    ```csharp theme={null}
    ImmutableAudience.Alias("76561190000000000", IdentityType.Steam, "internal-player-id-12345", IdentityType.Custom);
    ```
  </Accordion>

  <Accordion title="Reset()">
    Wipes the current player's identity, generates a fresh anonymous ID, and **discards queued events** (memory and disk) so the next player on the device isn't attributed to the previous one. Call when a player logs out.

    To send queued events before they're discarded:

    ```csharp theme={null}
    await ImmutableAudience.FlushAsync();
    ImmutableAudience.Reset();
    ```
  </Accordion>

  <Accordion title="DeleteData(string? userId = null)">
    Asks the backend to erase the player's data. Returns a `Task` you can await for acknowledgement, or discard for fire-and-forget.

    | Parameter | Type      | Description                                                                                        |
    | --------- | --------- | -------------------------------------------------------------------------------------------------- |
    | `userId`  | `string?` | Optional. The known user ID to target. When omitted (or `null`), the current anonymous ID is used. |

    ```csharp theme={null}
    await ImmutableAudience.DeleteData("76561190000000000");
    ```

    Erasure is server-side. After the request is accepted, also call `Reset` on the client so the next session isn't linked to the deleted user. See the [REST API](/docs/products/audience/rest-api#deleting-user-data) for the request shape.
  </Accordion>

  <Accordion title="FlushAsync()">
    Sends all queued events to the server immediately. Returns a `Task` that completes when the queue is empty or a backoff window prevents further sends. Call before `Reset` (which discards queued events) or `Shutdown` (which is capped at `ShutdownFlushTimeoutMs`) if you want every pending event delivered first.

    ```csharp theme={null}
    await ImmutableAudience.FlushAsync();
    ```
  </Accordion>

  <Accordion title="Shutdown()">
    Flushes any pending events (capped at `ShutdownFlushTimeoutMs`, default 2 seconds) and stops the SDK. Called automatically on `Application.quitting`. You do not need to call it yourself unless you want to control the flush timing or reinitialize with new config.

    ```csharp theme={null}
    ImmutableAudience.Shutdown();
    ```
  </Accordion>

  <Accordion title="RequestTrackingAuthorizationAsync() (iOS only)">
    Shows the iOS [App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency) prompt and returns the user's decision. The prompt appears once per app install; subsequent calls return the cached status without showing the prompt again.

    On non-iOS platforms, iOS 13, or before the SDK is initialized, resolves to `NotDetermined`.

    **Requires:** `AUDIENCE_MOBILE_ATTRIBUTION` define + `EnableMobileAttribution = true`.

    ```csharp theme={null}
    var status = await ImmutableAudience.RequestTrackingAuthorizationAsync();
    ```

    | Value           | Meaning                                                                       |
    | --------------- | ----------------------------------------------------------------------------- |
    | `NotDetermined` | Not yet prompted, dismissed, or non-iOS platform                              |
    | `Restricted`    | Restricted by device policy or parental controls. System prompt is not shown. |
    | `Denied`        | User denied tracking. IDFA is unavailable.                                    |
    | `Authorized`    | User authorized tracking. IDFA is included in the next `game_launch`.         |

    Call at a moment when the value exchange is clear to the player. Apple's HIG forbids prompting on cold launch. After the user responds, the SDK fires `tracking_authorization_changed` if the status differs from the previously stored value.
  </Accordion>

  <Accordion title="Diagnostic getters">
    Read-only properties for inspecting SDK state. Use these to verify the SDK is initialized, gate UI elements that depend on consent state (the Unity SDK does not currently ship dedicated `CanTrack` or `CanIdentify` helpers, so read `CurrentConsent` and compare against `ConsentLevel.Anonymous` or `ConsentLevel.Full` instead), or watch the queue size during development.

    | Property         | Type           | Description                                                                                                     |
    | ---------------- | -------------- | --------------------------------------------------------------------------------------------------------------- |
    | `Initialized`    | `bool`         | True between `Init()` and `Shutdown()`                                                                          |
    | `CurrentConsent` | `ConsentLevel` | The current consent level                                                                                       |
    | `UserId`         | `string?`      | Set by the most recent `Identify` call. Null before `Identify`, after `Reset`, or when consent is below `Full`. |
    | `AnonymousId`    | `string?`      | Persistent ID separate from `UserId` and `SessionId`. Null while consent is `None`.                             |
    | `SessionId`      | `string?`      | The current session's GUID. Null while consent is `None`.                                                       |
    | `QueueSize`      | `int`          | Number of unsent events (memory + disk)                                                                         |

    ```csharp theme={null}
    // Gate a tracking-dependent UI element on consent
    if (ImmutableAudience.CurrentConsent >= ConsentLevel.Anonymous)
    {
        ShowAnalyticsBadge();
    }

    // Force-flush the queue when it grows large
    if (ImmutableAudience.Initialized && ImmutableAudience.QueueSize > 100)
    {
        await ImmutableAudience.FlushAsync();
    }
    ```
  </Accordion>
</AccordionGroup>

## Event Delivery

* Events are batched and flushed every **5 seconds** or when **20 events** accumulate. Configure via the `FlushIntervalSeconds` and `FlushSize` fields on `AudienceConfig` (see [`Init`](#api-reference)).
* `Application.quitting` triggers a final flush capped at `ShutdownFlushTimeoutMs` (default 2 seconds).
* Events not flushed before quit are persisted to disk and shipped on the next launch. Events older than 30 days are discarded.

## Error Handling

Flushes run on a background thread with automatic retries. Most failures don't surface to your code. Pass an `OnError` callback when configuring [`Init`](#api-reference) to observe them.

`AudienceError.Code` is one of:

| Code                   | Description                                                                                                                                                                                                    |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `FlushFailed`          | An event batch failed to flush. Either a local storage read error (batch dropped) or a non-2xx, non-4xx server response (typically 5xx). On 5xx, the batch is retained and retried with exponential `backoff`. |
| `ValidationRejected`   | The server rejected an event batch with a 4xx status. The batch was dropped. Retrying will not help (typically a malformed payload).                                                                           |
| `ConsentSyncFailed`    | Failed to sync a consent change to the backend. The local consent level has already been applied. The server-side audit log may be out of date.                                                                |
| `NetworkError`         | A network call failed (exception, timeout, or non-2xx response on data deletion). Event batches are retained for retry. Data-delete requests are not retried automatically.                                    |
| `ConsentPersistFailed` | Failed to persist the consent level to disk. The in-memory level still applies but reverts on next launch.                                                                                                     |

## FAQ

<AccordionGroup>
  <Accordion title="Which scripting backends are supported?">
    Both **Mono** and **IL2CPP** are fully supported. Choose either in **Edit → Project Settings → Player → Other Settings → Scripting Backend**.
  </Accordion>

  <Accordion title="Does the SDK work in the Editor?">
    Yes. Init, Track, session lifecycle, and flush all work in Play Mode and Edit Mode test runners. Events fired in the Editor reach the same pipeline as builds. Set `TestMode = true` in your `AudienceConfig` during development to mark events as test traffic and keep them separate from production data in analytics.
  </Accordion>

  <Accordion title="What happens if I call Track before consent is set?">
    The call is a no-op. No event is queued, no warning is logged. Once `SetConsent(ConsentLevel.Anonymous)` (or higher) runs, subsequent Track calls flow normally. To verify state during development, read `ImmutableAudience.CurrentConsent` or the other [diagnostic getters](#diagnostic-getters).
  </Accordion>

  <Accordion title="Can I use the Unity SDK alongside the Web SDK or Tracking Pixel?">
    Yes. The three integrations cover different parts of your player journey. Unity captures in-game actions, the Web SDK covers web games and single-page apps, and the Tracking Pixel handles marketing pages and landing sites. All events flow to the same pipeline. See the [Data Dictionary](/docs/products/audience/data-dictionary) for the full per-event schema.
  </Accordion>

  <Accordion title="How do I handle a GDPR erasure request?">
    Erasure is server-side. From your backend, call `DELETE /v1/audience/data` with the user's `userId` or `anonymousId`. See [Deleting User Data](/docs/products/audience/rest-api#deleting-user-data) for the request shape. From inside the game, call `ImmutableAudience.DeleteData(userId)` and then `ImmutableAudience.Reset()` so subsequent activity isn't linked to the erased user.

    ```csharp theme={null}
    await ImmutableAudience.DeleteData("76561190000000000");
    ImmutableAudience.Reset();
    ```
  </Accordion>

  <Accordion title="Where does the SDK store its data?">
    Under `Application.persistentDataPath` in a folder named `imtbl_audience`. The folder contains the anonymous ID, the persisted consent level, and any queued events that haven't been flushed yet. Deleting it resets the SDK's local state on next launch.
  </Accordion>

  <Accordion title="How does identity work across the marketing site and the game?">
    Call `Identify` with the same `userId` on both surfaces at login. Events from both sessions are attributed to that player automatically, no `Alias` call needed.

    `Alias` is only needed when the same player uses different provider IDs on different surfaces. For example, if the player is identified as a Steam user in-game but later links a Passport account, call `Alias` to tell the backend they are the same person. See [Identity Stitching](/docs/products/audience/data-dictionary#identity-stitching) in the Data Dictionary.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Sample app" icon="gamepad" href="https://github.com/immutable/unity-immutable-sdk/tree/main/examples/audience">
    Working Unity project with Init, consent, Track, and Identify wired up
  </Card>

  <Card title="Attribution" icon="route" href="/docs/products/audience/attribution">
    How tracking data powers player attribution and Hub reports
  </Card>

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

  <Card title="Tracking Pixel" icon="crosshairs" href="/docs/products/audience/tracking-pixel">
    Passive tracking snippet for marketing sites and landing pages
  </Card>

  <Card title="Web SDK" icon="code" href="/docs/products/audience/web-sdk">
    Typed SDK for web games, marketing sites, and SPAs
  </Card>

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

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

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