> For the complete documentation index, see [llms.txt](https://docs.metica.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.metica.com/api/unity-sdk/unity-analytics-sdk.md).

# Unity Analytics SDK

## Analytics

Alongside SmartFloors ad optimization, the Metica SDK provides first-party event collection for player behaviour, monetization, and user state. Analytics shares the SDK's configuration and initialization — once the analytics package is installed and the SDK is initialized, log events through `MeticaSdk.Analytics`:

```csharp
MeticaSdk.Analytics.LogCustomEvent("levelComplete", new Dictionary<string, object>
{
    ["levelIndex"] = 12,
    ["stars"] = 3
});
```

### Prerequisites

Before using Metica Analytics, you need:

1. **API Key** — obtained from the Metica platform
2. **App ID** — obtained from the Metica platform
3. A **MAX SDK Key**, obtainable from the AppLovin platform (only required when initializing the SDK with ads)

### Installation

{% hint style="info" %}
If you haven't done so already, install the Metica SDK first by following the [Unity SDK integration guide](https://docs.metica.com/api/unity-sdk/unity-sdk-2). Analytics is part of the same SDK package — no separate SDK installation is required.
{% endhint %}

The Analytics feature requires one additional package on top of the Metica SDK:

1. Download [`com.metica.analytics.abstractions-1.0.0.tgz`](https://github.com/meticalabs/metica-unity-analytics/releases/tag/v1.0.0)
2. In Unity Editor, go to **Window → Package Manager**
3. Click the **+** button in the top-left corner and select **Add package from tarball...**
4. Select the downloaded `com.metica.analytics.abstractions-1.0.0.tgz`

The SDK detects the package automatically — once it is installed, the Analytics API is compiled in and `MeticaSdk.Analytics` becomes available. No scripting defines need to be set manually.

### Initialization

{% hint style="warning" %}
**Analytics does not need to be initialized separately.** The SDK is initialized once — when the analytics package is installed, the standard `MeticaSdk.Initialize` / `MeticaSdk.InitializeAsync` call also initializes Analytics, and `MeticaSdk.Analytics` is ready to use after it completes. The early-session flow below is only needed if you want Analytics available earlier in the app lifecycle than your standard SDK initialization.
{% endhint %}

```csharp
var config = new MeticaInitConfig("YOUR_API_KEY", "YOUR_APP_ID", "YOUR_USER_ID");
var mediationInfo = new MeticaMediationInfo(MeticaMediationType.MAX, "YOUR_MAX_SDK_KEY");
var initResponse = await MeticaSdk.InitializeAsync(config, mediationInfo);
```

#### Capturing early-session events

Some integrations defer `InitializeAsync` until later in the boot flow — waiting on user consent, a loading screen, or remote configuration. Events cannot be logged before the SDK is initialized, so early-session events such as `install` and `sessionStart` would be lost.

For this case only, initialize Analytics on its own as soon as the app starts. Analytics is fully active from this call — events are recorded and delivered normally. The later `InitializeAsync` call adds SmartFloors on top of the running SDK:

```csharp
// At app start: Analytics only
var config = new MeticaInitConfig("YOUR_API_KEY", "YOUR_APP_ID", "YOUR_USER_ID");
MeticaSdk.InitializeAnalytics(config);

// ... early-session events (install, sessionStart, ...) can be logged from here on ...

// Later, after consent / when ads are needed: add SmartFloors
var mediationInfo = new MeticaMediationInfo(MeticaMediationType.MAX, "YOUR_MAX_SDK_KEY");
var initResponse = await MeticaSdk.InitializeAsync(config, mediationInfo);
```

Pass the **same** `MeticaInitConfig` to both calls. If the second call supplies a different configuration, the SDK keeps the configuration from the first call and logs a warning.

If you initialize the SDK at app start (the standard flow), you do not need `InitializeAnalytics` — skip this and use the single `InitializeAsync` call above.

### User identity

Every event carries a `userId`. You can either:

* **Supply your own** stable user ID via `MeticaInitConfig`, or
* **Let the SDK generate one.** The SDK derives a deterministic UUID from a hash of a device identifier combined with your `appId`, so the generated ID remains consistent for a given device and app across launches. On Android the underlying identifier survives app reinstalls; on iOS it can be reset by the OS when all apps from the same vendor are uninstalled.

If you have your own account system, supplying your own ID is recommended so that events can be joined with your other data sources.

### Sessions

A **session** represents a continuous span of user activity within your game.

* **Session start** — the first activity after launch, or after at least 30 minutes of inactivity.
* **Session end** — the game stops producing events for 30 minutes.
* **Session length** — time of last recorded activity minus time of session start.

The SDK derives `sessionCount` and `lastSessionLength` from this definition and attaches them to every event automatically (see Environment attributes). Note that the `sessionStart` **event** is logged by your game (see sessionStart) — the SDK's session bookkeeping runs regardless, but the event itself is not emitted automatically.

### Event schema

Events fall into two categories:

* **Core events** — predefined events with a fixed schema, handled by the SDK with minimal developer input.
* **Custom events** — developer-defined events for use cases not covered by core events.

#### Base properties

Every event, core or custom, carries the following properties. All of them are populated automatically by the SDK — you never set them directly.

| Property        | Type   | Description                                                               |
| --------------- | ------ | ------------------------------------------------------------------------- |
| `eventType`     | string | Identifies the type of event (e.g. `purchase`, or your custom event name) |
| `eventId`       | string | Unique ID of this event, generated by the SDK                             |
| `appId`         | string | Your Metica app ID                                                        |
| `eventTime`     | number | Unix timestamp in milliseconds, set by the SDK at logging time            |
| `userId`        | string | The user ID (supplied or SDK-generated, see User identity)                |
| `customPayload` | object | Optional developer-supplied properties (see rules below)                  |

In addition, the SDK attaches the environment attributes to every event. In the delivered payload these appear as top-level fields on the event, alongside the base properties above.

**`customPayload` rules:**

* A single flat object — **nested objects are not permitted**.
* Values must be primitives (string, number, boolean) or **arrays of up to 100 elements** of the same primitive type.
* At most **100 properties** per payload. String values longer than **200 characters** are truncated. The same limits apply to `userStateAttributes`.
* Allowed on all core events **except** `fullStateUpdate` and `partialStateUpdate`.
* For custom events, `customPayload` carries the event's properties.

**Property type consistency:** once a property has been sent with a specific type, all future events — from your game or any other game in your organization — must use the same type for that property. Violating this causes errors and missing data in the ingestion pipeline. To change a property's type, stop sending it and introduce a new property name.

#### Environment attributes

The SDK automatically collects the following properties and attaches them to every event:

| Attribute            | Type    | Platform     | Description                                                          |
| -------------------- | ------- | ------------ | -------------------------------------------------------------------- |
| `appVersion`         | string  | All          | Host app's version (e.g. `1.4.2`)                                    |
| `meticaNativeSdk`    | string  | All          | Metica native SDK version                                            |
| `meticaUnitySdk`     | string  | All          | Metica Unity SDK version (when used from Unity)                      |
| `platform`           | string  | All          | `android` or `ios`                                                   |
| `osVersion`          | string  | All          | Operating system version (e.g. `14`, `17.4`)                         |
| `buildNumber`        | string  | All          | OS build identifier                                                  |
| `deviceModel`        | string  | All          | Device model identifier (e.g. `Pixel 7`, `iPhone15,2`)               |
| `deviceManufacturer` | string? | Android only | Hardware manufacturer (e.g. `Google`, `Samsung`)                     |
| `deviceType`         | string  | All          | `phone` or `tablet`                                                  |
| `deviceMemory`       | long    | All          | Total device RAM in megabytes                                        |
| `locale`             | string  | All          | BCP-47 language tag (e.g. `en-US`)                                   |
| `localTimezone`      | string  | All          | IANA time zone identifier (e.g. `Europe/London`)                     |
| `country`            | string? | All          | ISO 3166-1 alpha-2 country code, lowercased                          |
| `storeCountry`       | string? | iOS only     | App store storefront country (when available)                        |
| `gaid`               | string? | Android only | Google Advertising ID (lowercased); null when unavailable            |
| `idfa`               | string? | iOS only     | Apple Identifier For Advertisers; null when tracking is unauthorized |
| `idfv`               | string? | iOS only     | Apple Identifier For Vendor; null when tracking is unauthorized      |
| `sessionCount`       | long    | All          | Number of sessions started by this user on this device               |
| `lastSessionLength`  | long    | All          | Duration of the previous session, in seconds                         |

Attributes marked nullable (`?`) may be absent from the payload when the platform cannot resolve them — for example `gaid`/`idfa`/`idfv` when the user has not granted tracking consent.

#### Core events

Core events have a fixed schema. The base properties above are always included and are omitted from the field lists below.

**purchase**

Log a completed (or failed) in-app purchase.

| Field          | Type   | Required | Description                                   |
| -------------- | ------ | -------- | --------------------------------------------- |
| `productId`    | string | yes      | Store product identifier                      |
| `currencyCode` | string | yes      | ISO 4217 currency code (e.g. `USD`)           |
| `totalAmount`  | number | yes      | Purchase amount in the given currency         |
| `status`       | string | yes      | Purchase outcome (e.g. `completed`, `failed`) |
| `errorCode`    | string | no       | Error code when the purchase failed           |
| `referenceId`  | string | no       | Store transaction / receipt reference         |

```csharp
MeticaSdk.Analytics.LogPurchaseEvent(
    productId: "com.yourgame.gems_100",
    currency: "USD",
    amount: 4.99,
    status: "completed",
    errorCode: null,
    referenceId: "GPA.1234-5678",
    customPayload: null);
```

**sessionStart**

Marks the beginning of a session. No additional fields.

```csharp
MeticaSdk.Analytics.LogSessionStartEvent(customPayload: null);
```

**install**

Log this once, on the first launch after installation.

| Field        | Type   | Required | Description                                  |
| ------------ | ------ | -------- | -------------------------------------------- |
| `appVersion` | string | yes      | App version at install time (set by the SDK) |

```csharp
MeticaSdk.Analytics.LogInstallEvent(customPayload: null);
```

**impression**

Log an ad impression, including the revenue it generated. Use this only for ad sources that are **not** mediated through the Metica SDK — revenue for ads shown through the Metica SDK is reported automatically (as `estimatedAdRevenue` events).

| Field            | Type   | Required | Description                                       |
| ---------------- | ------ | -------- | ------------------------------------------------- |
| `value`          | number | yes      | Revenue of the impression in USD (e.g. `0.00555`) |
| `impressionType` | string | yes      | Ad format (e.g. `Rewarded`, `Interstitial`)       |
| `mediator`       | string | yes      | Mediation platform (e.g. `AppLovin`)              |
| `source`         | string | yes      | Ad network that served the ad (e.g. `ironSource`) |
| `placement`      | string | no       | Placement in your game (e.g. `LevelSuccess`)      |

```csharp
MeticaSdk.Analytics.LogImpressionEvent(
    value: 0.00555,
    type: "Rewarded",
    mediator: "AppLovin",
    source: "ironSource",
    placement: "LevelSuccess",
    customPayload: null);
```

**fullStateUpdate**

A snapshot of **all** attributes of the user state, typically sent once per session. It **replaces** all previously sent user state attributes. `customPayload` is not allowed on this event.

| Field                 | Type   | Required | Description                   |
| --------------------- | ------ | -------- | ----------------------------- |
| `userStateAttributes` | object | yes      | Map of attribute name → value |

```csharp
MeticaSdk.Analytics.LogFullStateUpdateEvent(new Dictionary<string, object>
{
    ["playerLevel"] = 42,
    ["softCurrencyBalance"] = 1200,
    ["vipTier"] = "gold"
});
```

**partialStateUpdate**

Updates a subset of user state attributes as the player progresses. `customPayload` is not allowed on this event.

| Field                 | Type   | Required | Description                        |
| --------------------- | ------ | -------- | ---------------------------------- |
| `userStateAttributes` | object | yes      | Map of the changed attributes only |

```csharp
MeticaSdk.Analytics.LogPartialStateUpdateEvent(new Dictionary<string, object>
{
    ["playerLevel"] = 43
});
```

#### Custom events

Custom events let you track game-specific behaviour not covered by core events. A custom event consists of the base properties plus your properties, carried in `customPayload`.

```csharp
MeticaSdk.Analytics.LogCustomEvent("earnAsset", new Dictionary<string, object>
{
    ["asset"] = "coin",
    ["source"] = "monthlyDrive",
    ["type"] = "rewarded",
    ["amount"] = 12
});
```

**Rules:**

* Event names must be **camelCase**, letters only, no digits and no special characters (e.g. `levelComplete`, `itemPurchased`). This is enforced — events with invalid names are dropped.
* The core event names are **reserved** and must not be used: `purchase`, `impression`, `sessionStart`, `install`, `fullStateUpdate`, `partialStateUpdate`.
* Properties follow the `customPayload` rules above: primitives or arrays of primitives, no nested objects, at most 100 properties, and stable types per property name.

#### Validation and delivery

* All events are schema-validated before transmission. Events that fail validation are **dropped** and not retried.
* Validated events are batched, persisted locally, and delivered with automatic retry — events are not lost to transient network failures or app restarts.

### Building a custom analytics wrapper

Studios that ship many titles often want a shared, internal analytics library that standardises how each game describes its world while still routing events through Metica. The SDK supports this pattern through the **`Metica.Analytics.Abstractions`** package installed above.

| Layer                           | Owner       | Changes often? | Purpose                                                                   |
| ------------------------------- | ----------- | -------------- | ------------------------------------------------------------------------- |
| `Metica.Analytics.Abstractions` | Metica      | Rarely         | Tiny, stable contract — the `IMeticaAnalytics` interface                  |
| Metica Unity SDK                | Metica      | Often          | Full implementation — transport, retries, device info, ad mediation       |
| Your wrapper library            | Your studio | As needed      | Game- or genre-specific helpers that map gameplay state to event payloads |

Your wrapper depends only on the abstractions package, not on the full SDK — SDK upgrades do not require rebuilding or changing your wrapper code. `MeticaSdk.Analytics` implements `IMeticaAnalytics`, so your wrapper accepts the interface in its constructor and never references the concrete SDK:

```csharp
using Metica.Analytics.Abstractions;

public class PuzzleAnalytics
{
    private readonly IMeticaAnalytics _analytics;

    public PuzzleAnalytics(IMeticaAnalytics analytics)
    {
        _analytics = analytics;
    }

    public void LogLevelEndOfferImpression(string offerId, int levelIndex, int stars)
    {
        _analytics.LogCustomEvent("puzzleLevelEndOfferImpression", new Dictionary<string, object>
        {
            ["offerId"] = offerId,
            ["levelIndex"] = levelIndex,
            ["stars"] = stars,
        });
    }
}

// Wiring it up after initialization:
var puzzle = new PuzzleAnalytics(MeticaSdk.Analytics);
```

### API reference

The full logging API, available via `MeticaSdk.Analytics` after initialization. Nullable parameters (`?`) accept `null` but must still be passed explicitly:

```csharp
public interface IMeticaAnalytics
{
    void LogPurchaseEvent(
        string productId,
        string currency,
        double amount,
        string status,
        string? errorCode,
        string? referenceId,
        Dictionary<string, object>? customPayload);

    void LogSessionStartEvent(Dictionary<string, object>? customPayload);

    void LogInstallEvent(Dictionary<string, object>? customPayload);

    void LogImpressionEvent(
        double value,
        string type,
        string mediator,
        string source,
        string? placement,
        Dictionary<string, object>? customPayload);

    void LogFullStateUpdateEvent(Dictionary<string, object> attributes);

    void LogPartialStateUpdateEvent(Dictionary<string, object> attributes);

    void LogCustomEvent(string eventName, Dictionary<string, object>? properties);
}
```

**Initialization method:**

* `InitializeAnalytics(MeticaInitConfig)` → `void` — analytics-only initialization for capturing early-session events; `InitializeAsync` later upgrades the same instance to full mode.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.metica.com/api/unity-sdk/unity-analytics-sdk.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
