# 3. Send in-game event data

Sending sufficient in-game event and player attribute data to Airflux is essential for model training and ad display optimization. This step may take approximately 30 minutes to 1 hour, depending on the number of events and attributes you want to track.

<figure><img src="/files/zol4mAdTLCVez07be1F6" alt=""><figcaption></figcaption></figure>

***

## 1. Implement session tracking

To implement session tracking within a Unity project, integrate the following methods into the Airflux SDK as shown below.

The `NotifyAppForeground()` function should be called when the app gains focus, signaling the start of a user session. Conversely, `NotifyAppBackground()` should be called when the application is sent to the background, indicating the end of the session.

By integrating these calls, Airflux can collect session information, including session count, duration, and maximum session length.

{% tabs %}
{% tab title="Unity" %}

```csharp
using UnityEngine;

public class AirfluxBehaviour : MonoBehaviour
{
    void OnApplicationPause(bool pauseStatus)
    {
        if (pauseStatus)
        {
            Airflux.NotifyAppBackground();
        }
        else
        {
            Airflux.NotifyAppForeground();
        }
    }
}
```

{% endtab %}
{% endtabs %}

## 2. Send in-game event data

Use the `TrackEvent()` function to track key player actions within the game. The in-game event data plays a crucial role in enabling the Airflux AI model to learn player behavior patterns.&#x20;

<table><thead><tr><th width="192.7109375">Event Name</th><th>When to trigger</th></tr></thead><tbody><tr><td>Ad Impression</td><td>When the ad is shown to the user</td></tr><tr><td>Order Completed</td><td>When the in-app purchase is completed and the user receives the item (e.g., when a dialog appears confirming the purchase)</td></tr><tr><td>Achieve Level</td><td>When a stage ends (e.g., Success, Fail, Give-up, Retry)</td></tr><tr><td>Spend Credits</td><td>When the player spends in-game currency</td></tr></tbody></table>

{% hint style="warning" %}
Collecting and sending the event data specified in the [Required event data for Airflux integration](/airflux-reference/required-event-data-for-airflux-integration.md) to Airflux is essential for using Airflux.&#x20;
{% endhint %}

{% tabs %}
{% tab title="Unity" %}
The `TrackEvent()` function should be called when a key event is triggered. Refer to the table below for the detailed requirements for the key components.

<table><thead><tr><th width="109.57421875">Name</th><th width="123.3515625">Type</th><th>Description</th></tr></thead><tbody><tr><td>category</td><td>String</td><td><p><strong>Name of the event</strong></p><p>Only underscores are permitted as special characters; colons and other special characters are not allowed. If the collected data exceeds the maximum limit of 128 characters, only the initial 128 characters will be saved.</p></td></tr><tr><td>semanticAttributes</td><td>Dictionary&#x3C;string, object></td><td><p><strong>Semantic attributes of the event</strong> </p><p>Semantic attribute data collection is limited by type: up to 1024 characters for strings, and 64 bits for integers or floats.</p></td></tr><tr><td>customAttributes</td><td>Dictionary&#x3C;string, object></td><td><p><strong>Custom attributes of the even</strong>t</p><p>Custom attribute data collection is limited to 2,048 characters; exceeding the limit results in ERROR_MAX_LENGTH_EXCEEDED.</p></td></tr></tbody></table>

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.AD_IMPRESSION,
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 11 },
        { AirfluxAttribute.TRANSACTION_ID, "8065ef16-162b-4a82-b683-e51aefdda7d5" },
        { AirfluxAttribute.CURRENCY, "USD" }
    },
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "key", "value" }
    }
);
```

{% endtab %}
{% endtabs %}

### Sample codes for event

{% hint style="warning" %}
In-game events are essential data for model training. Clearly understand the purpose and timing of collecting each event, and ensure proper implementation for data transmission.&#x20;
{% endhint %}

<details>

<summary>Ad Impression (IAA-related event)</summary>

{% hint style="danger" %}
**Attention**

The in-app ad revenue data must be collected using the client-side SDK through mediation platform integrations and sent to Airflux.&#x20;
{% endhint %}

Track ad impressions when ad is shown to the player and collect relevant data, such as ad type, ad revenue, placement, and more. &#x20;

For example, when ad revenue is generated after an interstitial ad is shown, call the `TrackEvent()` function, set the event category to `AirfluxCategory.AD_IMPRESSION`, and add `"adType"` to `customAttributes` to send `interstitial` as a value.&#x20;

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Semantic Attribute</td><td>Currency</td><td>Currency for ad revenue</td><td>USD</td></tr><tr><td>Semantic Attribute</td><td>Value</td><td>Ad revenue amount</td><td>1.99</td></tr><tr><td>Custom Attribute</td><td>adType</td><td>The type of the ad</td><td>reward: Rewarded ad<br>interstitial: Interstitial ad<br>banner: Banner ad</td></tr><tr><td>Custom Attribute</td><td>ad_placement</td><td>Ad placement</td><td>rw_offline: Offline reward ad<br>rw_get_item: Rewarded ad for obtaining items like weapons, skins, etc.<br>rw_get_coin: Rewarded ad for earning coins<br>rw_get_gem: Rewarded ad for earning gems<br>rw_time_skip: Rewarded ad for reducing recovery time<br>int_next_stage: Interstitial ad that is presented when advancing to the next stage<br>bn_next_stage: Banner ad that is presented when advancing to the next stage</td></tr><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 month)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The stage number where interstitial or rewarded ads are presented after Success, Fail, Give-up, or Retry. Otherwise, null is collected.</td><td>main: 1, 2, ..., 550 (30 new stages added every month)<br>promotion: 1, 2, ...,100</td></tr><tr><td>Custom Attribute</td><td>reward_item</td><td>The reward earned by the player after engaging with a rewarded ad. For other ad types, null is collected.</td><td>coin: Number of coins earned<br>gem: Number of gems earned</td></tr></tbody></table>

#### Code example

```csharp
// Example: Ad revenue transmission from AdMob
Airflux.TrackEvent(
    category: AirfluxCategory.AD_IMPRESSION,
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 0.01 }, // Required: Ad revenue
        { AirfluxAttribute.CURRENCY, "USD" }, // Required: Currency code
        {
            AirfluxAttribute.AD_PARTNERS, new Dictionary<string, object>()
            {
                {
                    "mopub", new Dictionary<string, object>()
                    {
                        { "app_version", "5.18.0" },
                        { "adunit_id", "12345" },
                        { "adunit_name", "12345" },
                        { "adunit_format", "Banner" },
                        { "id", "12345" },
                        { "currency", "USD" },
                        { "publisher_revenue", 12345.123 },
                        { "adgroup_id", "12345" },
                        { "adgroup_name", "12345" },
                        { "adgroup_type", "12345" },
                        { "adgroup_priority", "12345" },
                        { "country", "kr" },
                        { "precision", "publisher_defined" },
                        { "network_name", "12345" },
                        { "network_placement_id", "12345" },
                        { "demand_partner_data", "12345" },
                    }
                }
            }
        },
    },
    customAttributes: new Dictionary<string, object>()
    {
        { "adType", "reward" }, // Ad type: reward, interstitial, etc.
        { "ad_placement", "main_banner" }, // Ad placement: main_banner,int_next_stage, etc.
        { "stage_type", "Main" }, // Stage type: Main, Event, etc.
        { "stage_number", "1" }, // Stage number where interstitial/rewarded ads are shown after Success, Fail, Give-up, or Retry; otherwise null
        { "reward_item", new Dictionary<string, object> {{"coin", 10}, {"gem", 20}} } // Reward from a rewarded ad; null for other ad types
    }
);

```

</details>

<details>

<summary>Order Completed (IAP-related event)</summary>

{% hint style="danger" %}
**Attention**

The in-app purchase revenue data must be collected using the client-side SDK and sent to Airflux. There might be a slight gap between the data sent to Airflux and the revenue data provided by vendors.
{% endhint %}

Track in-app purchases and relevant data such as item information, transaction ID, the purchased amount, the payment currency, and more. &#x20;

For example, when a dialog is prompted confirming a purchase of an item, call the `TrackEvent()` function, set the event category to `AirfluxCategory.ORDER_COMPLETED`, and add `AirfluxAttribute.PRODUCT_ID` and `AirfluxAttribute.PRODUCT_NAME` to send information of the purchase item.&#x20;

{% hint style="warning" %}
The payment currency information (`AirfluxAttribute.CURRENCY` )and the purchase amount  (`AirfluxAttribute.VALUE)` must be included in the event data for accurate revenue analysis.&#x20;
{% endhint %}

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Semantic Attribute</td><td>Transaction ID</td><td>Transaction ID</td><td>TXN-20250411-5F3C9A72B1</td></tr><tr><td>Semantic Attribute</td><td>Currency</td><td>Currency for ad revenue</td><td>USD</td></tr><tr><td>Semantic Attribute</td><td>Value</td><td>Ad revenue amount</td><td>10.99</td></tr><tr><td>Semantic Attribute</td><td>Product ID</td><td>Product ID</td><td>1C569KY32P1</td></tr><tr><td>Semantic Attribute</td><td>Product Name</td><td>Product Name</td><td>remove_ads: ""Ad Removal"" as a purchase item<br>welcome_pack: Item package for newly acquired players<br>starter_pack: Item package for beginners<br>coin_pack_1: Coin package<br>gem_pack_2: Gem package<br>limited_skin_1: Time-limited skin</td></tr><tr><td>Custom Attribute</td><td>purchase_route</td><td>The source of the purchase</td><td>shop: Purchased from the shop<br>popup: Purchased from a pop-up</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.ORDER_COMPLETED, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>()
    {
        { AirfluxAttribute.VALUE, 11 }, // Required: Actual purchase amount
        { AirfluxAttribute.TRANSACTION_ID, "8065ef16-162b-4a82-b683-e51aefdda7d5" }, // Required: Transaction ID
        { AirfluxAttribute.CURRENCY, "USD" }, // Required: Currency code 
        {
            AirfluxAttribute.PRODUCTS, new List<object>()
            {
                new Dictionary<string, object>()
                {
                    { AirfluxAttribute.PRODUCT_ID, "1C569KY32P1" }, // Required, Product ID
                    { AirfluxAttribute.PRODUCT_NAME, "remove_ads" } // Required, Product name (welcome_back, remove_ads, starter_pack ...)
                } 
            }
        }
    },
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "purchase_route", "shop" } // (Optional) shop / popup
    }
```

</details>

<details>

<summary>Achieve Level</summary>

Track player game progress and how a stage ended.&#x20;

For example, when a stage ends, call the `TrackEvent()` function, set the event category to `AirfluxCategory.ACHIEVE_LEVEL`,  and add `"stage_type"` and `"stage_number"` to track the stage type and stage number.&#x20;

Additionally, use `"result"` to send information on how the stage ended, such as `success`, `fail`, `giveup`, and `retry` .

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="127.4609375">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 month)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The stage number where Success, Fail, Give-up, or Retry occurred.</td><td>main: 1, 2, ..., 550 (30 new stages added every month)<br>promotion: 1, 2, ...,100 </td></tr><tr><td>Custom Attribute</td><td>result</td><td>The result of the stage</td><td>success: Stage completed successfully<br>fail: Stage failed<br>giveup: Stage abandoned<br>retry: Stage retried after failure or exit</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.ACHIEVE_LEVEL, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>(),
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "stage_type", "main"},
        { "stage_number", 13  },
        { "result", "success" }      
    }
);
```

</details>

<details>

<summary>Spend Credits</summary>

Track in-game currency spending and relevant data, such as in-game currency information, spending amount, and more.&#x20;

For example, when the player spends in-game currency, such as coins and gems, call the `TrackEvent()` function, set the event category to `AirfluxCategory.SPEND_CREDITS` and add `"item_type"` and `"item_amount"`  to send the in-game currency type and spending amount. &#x20;

Additionally, use `"stage_type"` and `"stage_number"`to to track the stage type and stage number.

<table><thead><tr><th width="169.89453125">Attribute Type</th><th width="115.31640625">Name</th><th width="253.59765625">Semantic Attributes Description</th><th>Sample Value</th></tr></thead><tbody><tr><td>Custom Attribute</td><td>item_type</td><td>The type of the in-game currency spent</td><td>coin<br>gem</td></tr><tr><td>Custom Attribute</td><td>item_amount</td><td>The amount of the in-game currency spent</td><td>10, 20</td></tr><tr><td>Custom Attribute</td><td>stage_type</td><td>The type of the stage</td><td>main: Main stage<br>promotion: Seasonal promotion stage (updated every 3 month)</td></tr><tr><td>Custom Attribute</td><td>stage_number</td><td>The current stage of the player</td><td>main: 1, 2, ~ , 550 (30 new stages added every month)<br>promotion: 1, 2, ~ ,100</td></tr></tbody></table>

#### Code example

```csharp
Airflux.TrackEvent(
    // StandardCategory
    category: AirfluxCategory.SPEND_CREDITS, // or "CustomEvent" (CustomCategory)
    // SemanticAttributes
    semanticAttributes: new Dictionary<string, object>(),
    // CustomAttributes
    customAttributes: new Dictionary<string, object>()
    {
        { "item_type", "coin" },
        { "item_amount", 100  },
        { "stage_type", "main"},
        { "stage_number", 13 }
    }
);
```

</details>

#### In-game event data verification

<details>

<summary>Using the Airflux Testing Console</summary>

For a more structured validation, use the **Event Data Test** area within the [Airflux Testing Console](https://testing-console.airflux.ai/).

Once all events have been collected successfully, the status for each event should be either **No Data** or **Nullable Fields Incomplete**.

</details>

## 3. Send player attribute data

In addition to tracking player actions through in-game events, Airflux also requires player attribute data, such as the player’s current level, in-game currency balance, and other contextual information to train the Airflux AI model. Sufficient player attribute data allows Airflux to fine-segment players and perform inferences for maximum LTV and retention.&#x20;

{% hint style="warning" %}
Attention

The player attribute data must be passed to the SDK before the inference API request.&#x20;
{% endhint %}

Use the following functions to track and pass the player attribute data to the SDK.

<details>

<summary>Player's Level Status </summary>

Use `Airflux.SetLevel()`  to pass the player's level status to the Airflux SDK. For accurate model training, the player's level status data must be aggregated to gather sufficient contextual information.&#x20;

Therefore, it is crucial to call the `Airflux.SetLevel()`  and aggregate the player's level status every time:

* the player opens the game
* the player logs in to the game&#x20;
* the player's game level is updated

For new players or sign-ups, the starting level should be passed.

<pre class="language-csharp"><code class="lang-csharp">// Trigger in the following cases
// - when the player starts the game
// - when the player logs in
<strong>// - when the player level is updated
</strong>Airflux.SetLevel(5);
</code></pre>

</details>

<details>

<summary>Player's Currency Status</summary>

Use `Airflux.SetHardCurrency()`  and `Airflux.SetSoftCurrency()`  to pass the player's hard currency (e.g., diamonds) and soft currency (e.g., coins) inventory status to the Airflux SDK.

For accurate model training, the player's currency status data must be aggregated to gather sufficient contextual information.&#x20;

Therefore, it is crucial to call the `Airflux.SetHardCurrency()`  or `Airflux.SetSoftCurrency()` and aggregate the player's currency status every time:

* a player purchased, earned, or spent in-game currency
* a player starts the game
* a player logs in to the game&#x20;

For new players or sign-ups, the game's default currency inventory status should be passed.

```csharp
// Trigger when hard/soft currency balances change. Input the final balance.
// - Each currency can have up to 100 key-value pairs.
// - The attribute names must satisfy the regex ^[a-zA-Z_][a-zA-Z0-9_]*$.
// - The maximum length of attribute names is 128 characters.
Airflux.SetHardCurrency("diamond", 1000);
Airflux.SetSoftCurrency("gold", 1000);
Airflux.SetSoftCurrency("wood", 1000);
Airflux.SetSoftCurrency("coal", 1000);
```

{% hint style="warning" %}
**Attention**&#x20;

Input the in-game currency balance for `Currency` . If a player had 500 diamonds and purchased 700 more, `Airflux.SetHardCurrency("diamond", 1200)` should be triggered.
{% endhint %}

</details>

<details>

<summary>Other Player Attribute</summary>

Use `Airflux.SetInferenceAttribute()` to pass the player's attributes other than the level and currency inventory status. Make sure to call the function whenever the attribute is updated.&#x20;

```csharp
// Trigger when player attributes change.
// - Attributes can have up to 100 key-value pairs.
// - Keys must satisfy the regex ^[a-zA-Z_][a-zA-Z0-9_]*$.
// - The maximum length of keys is 128 characters.
// - Values type must be string, numeric, or boolean.
// - The maximum length of string values is 1024 characters.
Airflux.SetInferenceAttribute("string", "string");
Airflux.RemoveInferenceAttribute("string");
Airflux.ClearInferenceAttributes();
```

</details>

#### Player attribute data verification

<details>

<summary>Using the Airflux Testing Console</summary>

For a more structured validation, use the **Player Attriubte Test** area within the [Airflux Testing Console](https://testing-console.airflux.ai/).

Once all events have been collected successfully, the status for each event should be either **No Data** or **Nullable Fields Incomplete**.

</details>

## 4. Send User ID

If your game issues a unique User ID for each player upon sign-up or sign-in, it is advised to send the data to Airflux. If your game server does not handle User IDs, a unique identifier can be generated upon sign-in and sent to Airflux. The User ID must be sent before the event data.

{% tabs %}
{% tab title="Unity" %}
{% hint style="danger" %}
**Attention**

If the User ID is not sent before the event data, the User ID cannot be linked to the event data.&#x20;
{% endhint %}

```csharp
// Send the User ID
Airflux.SetUserID("your_internal_user_id");

// Send the event data
Airflux.TrackEvent(
    // ... event related codes ...
);
```

{% endtab %}
{% endtabs %}

## 5. Verify data transmission

Ensure the payload transmitted by the Airflux SDK adheres to the predefined event taxonomy and that the session information, in-game events, and play attribute data are sent to the Airflux server as intended.

### How to verify event transmission

Trigger events based on the test scenarios listed below, and check the corresponding events in the **\[Raw Data]>\[App Real-time Log]** menu of the Airbridge dashboard. Event data transmitted through the Airflux SDK will be displayed in JSON format. You need to verify that the data type and structure of each field match the predefined format.

<figure><img src="/files/Oah7MQrlTytyNxzDcOmX" alt=""><figcaption></figcaption></figure>

### Taxonomy-based event and attribute format validation

Check the following items to ensure that the taxonomy definitions match the values configured in the SDK.

<table><thead><tr><th width="210">Field</th><th>Validation Criteria</th></tr></thead><tbody><tr><td>eventData.goal.category</td><td>Verify that the event name exactly matches the string defined in the taxonomy.</td></tr><tr><td>semanticAttributes</td><td>• All keys must match those defined in the taxonomy.<br>• Value types must match the defined types (string, number, boolean).<br>• For revenue events (ad_impression, order_completed), values must be positive numbers.</td></tr><tr><td>originalCurrency</td><td>Must be a 3-letter uppercase code defined by ISO-4217 (e.g., USD, KRW)</td></tr><tr><td>customAttributes</td><td>Key: string / Value: allowed types (string, number, boolean)</td></tr></tbody></table>

### Key event validation based on test scenarios

When the following key events are triggered, verify that they are properly logged in the **\[App Real-time Log]** page without omission and that related attributes are accurately included.&#x20;

Click [here](/airflux-reference/required-event-data-for-airflux-integration.md) to view the events and key attributes that must be sent to Airflux.&#x20;

<table><thead><tr><th width="254">Scenario</th><th>Key Event</th></tr></thead><tbody><tr><td>App installed</td><td>Install</td></tr><tr><td>App launched</td><td>Open</td></tr><tr><td>Ad viewing completed</td><td>Ad Impression</td></tr><tr><td>In-app purchase completed</td><td>Order Completed</td></tr><tr><td>Level achieved</td><td>Achieve Level</td></tr><tr><td>Currency spent</td><td>Spend Credits</td></tr></tbody></table>

In particular, revenue-related events such as **Ad Impression** and **Order Complete** are critical for the Airflux model training. Double-check the following points:

* **Ad Impression**
  * Confirm the revenue value in `eventData.originalValue`.
  * Confirm the currency in `originalCurrency`.
  * Confirm the ad type in `customAttributes.adType`.
* **Order Completed**
  * Confirm the revenue value in `eventData.originalValue`.
  * Confirm the currency in `originalCurrency`.

If you are sending User IDs, make sure the `externalUserID` is properly logged.

## Frequently Asked Questions

<details>

<summary>When a player completes a stage and levels up at the same time, how should I track it?</summary>

Use the `TrackEvent()` function to track the player's action of completing a stage as the Achieve Level event, and use the `SetLevel()` function to track the player's updated level as the player attribute.&#x20;

</details>

<details>

<summary>Can I use the Airflux SDK to collect and send game store payment data?</summary>

No. The game store payment data must be collected and sent using the client-side SDK.

</details>

<details>

<summary>After restarting the game on iOS, the Airflux SDK stops sending events. How can I fix this?</summary>

There are two things you need to check:

1. If `AutoStartTrackingEnabled` is set to `false` , make sure to manually call `Airflux.StartTracking()` every time the app launches.
2. Make sure `StartTracking()` is called after `application(_:didFinishLaunchingWithOptions:)`, the core method in the iOS app lifecycle that is executed once native initialization is complete.\
   Calling `StartTracking()` too early may result in all events being dropped starting from the second launch.

For Airflux Unity SDK integration, we recommend the following approach:

1. Create a `MonoBehaviour` script.
2. Call `Airflux.StartTracking()` once within `OnApplicationFocus(true)`.

* `OnApplicationFocus()` is called after `didBecomeActive`, and therefore, the call occurs after native initialization.
* Because `OnApplicationFocus()` is triggered every time the app returns to the foreground, make sure to add a flag so that `StartTracking()` is only called once.

</details>


---

# Agent Instructions: 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:

```
GET https://docs.airflux.ai/airflux-integration-unity-v0.x/3.-send-in-game-event-data.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
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.
