# Basic methods

Please take a look at our [Expert tips](https://docs.devtodev.com/integration/expert-tips/what-to-track) before integrating the events.

## Real Payment

To track payments in a real currency, dispatch this event right after the system validates that the payment went through successfully. The event is fundamental and mandatory for all the app metrics related to monetization.

{% hint style="warning" %}
For purchases made through the App Store and Google Play Market, [automatic data collection](https://docs.devtodev.com/integration/autocapture/automatic-payment-tracking) for in-app payments is available starting from SDK version **2.5.0** and higher. To enable automatic data collection, provide valid credentials in the settings section under **Payments integration → IAP auto tracking**. Once the credentials are successfully verified, all new purchases made in your application will be automatically sent as a **Real Payment** event (with the source specified as auto).

We recommend avoiding simultaneous event sending through automatic tracking and manual submission of the basic **Real Payment** event, as this may lead to data duplication.
{% endhint %}

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}

```swift
DTDAnalytics.realCurrencyPayment(orderId: "Order ID", 
                                 price: 12.5, 
                                 productId: "Product ID", 
                                 currencyCode: "USD")
```

<table><thead><tr><th width="139">Parameter</th><th width="96">Type</th><th width="256">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>string</td><td>from 1 to 65 symbols</td><td>A unique transaction ID. Use <strong><code>transactionIdentifier</code></strong> property value in <strong><code>SKPaymentTransaction</code></strong> object in a complete transaction receipt.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>string</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from Double.min to Double.max</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>string</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>
{% endtab %}

{% tab title="iOS+macOS (Objective-C)" %}

```swift
[DTDAnalytics realCurrencyPaymentWithOrderId:@"Order ID"
                                       price:12.5
                                   productId:@"Product ID"
                                currencyCode:@"USD"];
```

<table><thead><tr><th width="153.5">Parameter</th><th width="133">Type</th><th width="261">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>NSString</td><td>from 1 to 65 symbols</td><td>A unique transaction ID. Use <strong><code>transactionIdentifier</code></strong> property value in <strong><code>SKPaymentTransaction</code></strong> object in a complete transaction receipt.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>NSString</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from Double.min to Double.max</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>NSString</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>
{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
DTDAnalytics.realCurrencyPayment(
    orderId = "Order ID",
    price = 12.5,
    productId = "Product ID",
    currencyCode = "USD"
)
```

<table><thead><tr><th width="139.5">Parameter</th><th width="99">Type</th><th width="281">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>string</td><td>from 1 to 65 symbols</td><td>A unique transaction ID. </td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>string</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from Double.min to Double.max</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>string</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>

{% hint style="info" %}
How to find the transaction ID in GooglePlay transaction?

Find the INAPP\_PURCHASE\_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP\_PURCHASE\_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.
{% endhint %}
{% endtab %}

{% tab title="Android (Java)" %}

```java
 DTDAnalytics.INSTANCE.realCurrencyPayment(
        "Order ID",
        12.5,
        "Product ID",
        "USD"
);
```

<table><thead><tr><th width="145.5">Parameter</th><th width="101">Type</th><th width="262">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>string</td><td>from 1 to 65 symbols</td><td>A unique transaction ID. </td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>string</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from Double.min to Double.max</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>string</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>

{% hint style="info" %}
How to find the transaction ID in GooglePlay transaction?

Find the INAPP\_PURCHASE\_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP\_PURCHASE\_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.
{% endhint %}
{% endtab %}

{% tab title=".NET Native + UWP" %}

```csharp
DTDAnalytics.RealCurrencyPayment(
    orderId: "Order ID",
    price: 12.5,
    productId: "Product ID",
    currencyCode: "USD");
```

<table><thead><tr><th width="141.5">Parameter</th><th width="81">Type</th><th width="336">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>string</td><td>from 1 to 65 symbols</td><td>A unique transaction ID.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>string</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from double.MinValue to double.MaxValue</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>string</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>
{% endtab %}

{% tab title="Unity" %}

```csharp
DTDAnalytics.RealCurrencyPayment(
    orderId: "Order ID",
    price: 12.5,
    productId: "Product ID",
    currencyCode: "USD");
```

<table><thead><tr><th width="161.5">Parameter</th><th width="115">Type</th><th width="347">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>string</td><td>from 1 to 65 symbols</td><td>A unique transaction ID.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>string</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from Double.MinValue to Double.MaxValue</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>string</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>
{% endtab %}

{% tab title="Web" %}

```javascript
analytics.realCurrencyPayment(
                                orderId,
                                price,
                                productId,
                                currencyCode)
```

<table><thead><tr><th width="143.5">Parameter</th><th width="101">Type</th><th width="244">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>string</td><td>from 1 to 65 symbols</td><td>A unique transaction ID.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>string</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>double</td><td>from Number.MIN_VALUE to Number.MAX_VALUE</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>string</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>
{% endtab %}

{% tab title="Unreal" %}
![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FSDbWLNFYpwd8IqUiIQu4%2Fimage.png?alt=media\&token=a9fd5b9a-5b77-4721-b5fe-58b773bb5127)

<table><thead><tr><th width="157.5">Parameter</th><th width="96">Type</th><th width="315">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>FString</td><td>from 1 to 65 symbols</td><td>A unique transaction ID.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>FString</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <strong>USD</strong>, <strong>EUR</strong> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>float</td><td>from float.MinValue to float.MaxValue</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>FString</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>

```cpp
1UDTDAnalyticsBPLibrary::RealCurrencyPayment("OrderId", 12.5, "ProductId", "USD");
```

{% endtab %}

{% tab title="Godot" %}

```swift
DTDAnalytics.RealCurrencyPayment("orderId", 9.99, "productId", "USD")
```

<table><thead><tr><th width="140.5">Parameter</th><th width="114">Type</th><th width="316">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>orderId</code></strong></em></td><td>String</td><td>from 1 to 65 symbols</td><td>A unique transaction ID.</td></tr><tr><td><em><strong><code>currencyCode</code></strong></em></td><td>String</td><td>precisely 3 symbols</td><td>Transaction currency (<a href="https://en.wikipedia.org/wiki/ISO_4217">ISO 4217 standard</a>) e.g. <em><strong>USD, EUR</strong></em> etc.</td></tr><tr><td><em><strong><code>price</code></strong></em></td><td>Float</td><td>from Double.min to Double.max</td><td>The item price in the transaction currency.</td></tr><tr><td><em><strong><code>productId</code></strong></em></td><td>String</td><td>from 1 to 255 symbols</td><td>Item name. We recommend using a bundle or names in the same language.</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

{% hint style="info" %}
By default (easy to change in the app’s settings) devtodev server invalidates transactions with previously-used identifiers. Besides, the server performs identifier checks by its outer appearance in order to avoid obvious fraud.

If you want to exclude fraud payments from your reports altogether, before creating ‘Real Currency Payment’ event, use devtodev anti-cheat feature.
{% endhint %}

## Custom Events

If you want to track non-basic events, you can create custom events of your own. How you are going to apply them depends solely on you.

{% hint style="warning" %}
Attention! We strongly recommend that you do not use custom event properties to transfer and store data that fits the definition of [personal data](https://gdpr-info.eu/issues/personal-data/)!
{% endhint %}

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}

```swift
DTDAnalytics.customEvent(eventName: "Event name")
```

If you want to pass custom parameters, use **`DTDCustomEventParameters`** class instance.

```swift
let parameters = DTDCustomEventParameters()
parameters.add(key: "key for string value", value: "string value")
parameters.add(key: "key for int value", value: 10)
parameters.add(key: "key for bool value", value: true)
parameters.add(key: "key for double value", value: 12.5)

DTDAnalytics.customEvent(eventName: "Event name", parameters: parameters)
```

| Parameter          | Type                     | Restrictions                                              | Description              |
| ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string                   | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | DTDCustomEventParameters | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`DTDCustomEventParameters`** object:

| Type         | Restrictions                  |
| ------------ | ----------------------------- |
| **`int`**    | from Int64.min to Int64.max   |
| **`string`** | from 1 to 255 symbols         |
| **`bool`**   | true/false                    |
| **`double`** | from Double.min to Double.max |
| {% endtab %} |                               |

{% tab title="iOS+macOS (Objective-C)" %}

```objectivec
[DTDAnalytics customEvent:@"Event name"];
```

If you want to pass custom parameters, use **`DTDCustomEventParameters`** class instance.

```objectivec
DTDCustomEventParameters *parameters = [[DTDCustomEventParameters alloc] init];
[parameters addString:@"key for string value" value:@"string value"];
[parameters addInt:@"key for int value" value:10];
[parameters addBool:@"key for bool value" value:true];
[parameters addDouble:@"key for double value" value:12.5];

[DTDAnalytics customEvent:@"Event name" withParameters:parameters];
```

| Parameter          | Type                     | Restrictions                                              | Description              |
| ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | NSString                 | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | DTDCustomEventParameters | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`DTDCustomEventParameters`** object:

| Type         | Restrictions                  |
| ------------ | ----------------------------- |
| **`int`**    | from Int64.min to Int64.max   |
| **`string`** | from 1 to 255 symbols         |
| **`bool`**   | true/false                    |
| **`double`** | from Double.min to Double.max |
| {% endtab %} |                               |

{% tab title="Android (Kotlin)" %}

```kotlin
DTDAnalytics.customEvent(eventName = "Event name")
```

If you want to pass custom parameters, use **`DTDCustomEventParameters`** class instance.

```java
let parameters = DTDCustomEventParameters()
parameters.add(key = "key for string value", value = "string value")
parameters.add(key = "key for int value", value = 10)
parameters.add(key = "key for bool value", value = true)
parameters.add(key = "key for double value", value = 12.5)

DTDAnalytics.customEvent(
    eventName = "Event name", 
    customEventParameters = parameters
)
```

| Parameter          | Type                     | Restrictions                                              | Description              |
| ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string                   | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | DTDCustomEventParameters | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`DTDCustomEventParameters`** object:

| Type          | Restrictions                  |
| ------------- | ----------------------------- |
| **`Long`**    | from Long.min to Long.max     |
| **`String`**  | from 1 to 255 symbols         |
| **`Boolean`** | true/false                    |
| **`Double`**  | from Double.min to Double.max |
| {% endtab %}  |                               |

{% tab title="Android (Java)" %}

```java
DTDAnalytics.INSTANCE.customEvent("Event name");
```

If you want to pass custom parameters, use **`DTDCustomEventParameters`** class instance.

```java
DTDCustomEventParameters parameters = new DTDCustomEventParameters();
parameters.add("key for string value", "string value");
parameters.add("key for int value", 10);
parameters.add("key for bool value", true);
parameters.add("key for double value", 12.5);
DTDAnalytics.INSTANCE.customEvent("Event name", parameters);
```

| Parameter          | Type                     | Restrictions                                              | Description              |
| ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string                   | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | DTDCustomEventParameters | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`DTDCustomEventParameters`** object:

| Type          | Restrictions                  |
| ------------- | ----------------------------- |
| **`Long`**    | from Long.min to Long.max     |
| **`String`**  | from 1 to 255 symbols         |
| **`Boolean`** | true/false                    |
| **`Double`**  | from Double.min to Double.max |
| {% endtab %}  |                               |

{% tab title=".NET Native + UWP" %}

```csharp
DTDAnalytics.CustomEvent(eventName: "Event name");
```

If you want to pass custom parameters, use **`DTDCustomEventParameters`** class instance.

```csharp
var parameters = new DTDCustomEventParameters();
parameters.Add(key: "key for string value", value: "string value");
parameters.Add(key: "key for int value", value: 10);
parameters.Add(key: "key for bool value", value: true);
parameters.Add(key: "key for double value", value: 12.5);
DTDAnalytics.CustomEvent(eventName: "Event name", parameters: parameters);
```

| Parameter          | Type                     | Restrictions                                              | Description              |
| ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string                   | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | DTDCustomEventParameters | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`DTDCustomEventParameters`** object:

| Type         | Restrictions                            |
| ------------ | --------------------------------------- |
| **`long`**   | from long.MinValue to long.MaxValue     |
| **`string`** | from 1 to 255 symbols                   |
| **`bool`**   | true/false                              |
| **`double`** | from double.MinValue to double.MaxValue |
| {% endtab %} |                                         |

{% tab title="Unity" %}

```csharp
DTDAnalytics.CustomEvent(eventName: "Event name");
```

If you want to pass custom parameters, use **`DTDCustomEventParameters`** class instance.

```csharp
var parameters = new DTDCustomEventParameters();
parameters.Add(key: "key for string value", value: "string value");
parameters.Add(key: "key for int value", value: 10);
parameters.Add(key: "key for bool value", value: true);
parameters.Add(key: "key for double value", value: 12.5);
DTDAnalytics.CustomEvent(eventName: "Event name", parameters: parameters);
```

| Parameter          | Type                     | Restrictions                                              | Description              |
| ------------------ | ------------------------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string                   | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | DTDCustomEventParameters | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`DTDCustomEventParameters`** object:

| Type         | Restrictions                            |
| ------------ | --------------------------------------- |
| **`long`**   | from Int64.MinValue to Int64.MaxValue   |
| **`string`** | from 1 to 255 symbols                   |
| **`bool`**   | true/false                              |
| **`double`** | from Double.MinValue to Double.MaxValue |
| {% endtab %} |                                         |

{% tab title="Web" %}

```javascript
analytics.customEvent(eventName, parameters)
```

If you want to pass custom parameters, use an object with parameters.

```javascript
analytics.customEvent("Event name", {
        "key for string value" : "string value",
        "key for int value": 10,
        "key for bool value": true,
        "key for double value": 12.5
})
```

| Parameter          | Type   | Restrictions                                              | Description              |
| ------------------ | ------ | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | object | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using parameters object:

| Type         | Restrictions                                                |
| ------------ | ----------------------------------------------------------- |
| **`long`**   | from Number.MIN\_SAFE\_INTEGER to Number.MAX\_SAFE\_INTEGER |
| **`string`** | from 1 to 255 symbols                                       |
| **`bool`**   | true/false                                                  |
| **`double`** | from Number.MIN\_VALUE to Number.MAX\_VALUE                 |
| {% endtab %} |                                                             |

{% tab title="Unreal" %}
![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FNTKRdc275B5Nt3dCWt88%2Fimage.png?alt=media\&token=1acf17ba-388e-4103-a135-8d6208852389)

| Parameter         | Type    | Restrictions         | Description        |
| ----------------- | ------- | -------------------- | ------------------ |
| ***`eventName`*** | FString | from 1 to 72 symbols | Custom event name. |

```cpp
UDTDAnalyticsBPLibrary::CustomEvent("EventName");
```

If you want to pass custom parameters, use:

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FMrgNCiPQv15AcxQousNU%2Fimage.png?alt=media\&token=4f0d968d-3a46-4bbf-8928-3b7d8e82cf46)

| Parameter          | Type                  | Restrictions                                                                                                                                                                                                                                                      | Description                                                                                                                                                                                          |
| ------------------ | --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`***  | FString               | from 1 to 72 symbols                                                                                                                                                                                                                                              | Custom event name.                                                                                                                                                                                   |
| ***`parameters`*** | FDTDCustomEventParams | <ul><li>StringParameters (TMap\<FString, FString>)</li><li>IntParameters (TMap\<FString, int64>)</li><li>FloatParameters (TMap\<FString, float>)</li><li>BoolParameters (TMap\<FString, bool>)</li></ul><p>key - from 1 to 32 symbols</p><p>value - see below</p> | <p>Custom event parameters.</p><p><strong>Warning:</strong> avoid duplicate keys in all dictionaries. Because dictionaries are combined into a generic dictionary \[string: any] in native code.</p> |

The following data types can be passed using the **DTDCustomEventParameters** object:

| Type    | Restrictions                          |
| ------- | ------------------------------------- |
| int64   | from int64.MinValue to int64.MaxValue |
| FString | from 1 to 255 symbols                 |
| bool    | true/false                            |
| float   | from float.MinValue to float.MaxValue |

```cpp
FDTDCustomEventParams params;
params.BoolParameters.Add("BoolKey", true);
params.FloatParameters.Add("FloatKey", 3.3);
params.IntParameters.Add("IntKey");
params.StringParameters.Add("StringKey", "StringValue");
UDTDAnalyticsBPLibrary::CustomEventWithParams("EventName", params);
```

{% endtab %}

{% tab title="Godot" %}

```gdscript
DTDAnalytics.CustomEvent("CustomEvent")
```

If you want to pass custom parameters, use **`GDDTDCustomEventParameters`** class instance.

```gdscript
var parameters = GDDTDCustomEventParams.new()
parameters.AddStringValue("str_key", "str_value")
parameters.AddBoolValue("bool_key", true)
parameters.AddIntegerValue("int_key", 100)
parameters.AddFloatValue("float_key", 0.0015)
DTDAnalytics.CustomEventWithParams("CustomEventWithParams", parameters)
```

| Parameter          | Type                   | Restrictions                                              | Description              |
| ------------------ | ---------------------- | --------------------------------------------------------- | ------------------------ |
| ***`eventName`***  | string                 | from 1 to 72 symbols                                      | Custom event name.       |
| ***`parameters`*** | GDDTDCustomEventParams | <p>key - from 1 to 32 symbols</p><p>value - see below</p> | Custom event parameters. |

The following data types can be passed using the **`GDDTDCustomEventParameters`** object:

| Type          | Restrictions                |
| ------------- | --------------------------- |
| **`int`**     | from Int64.min to Int64.max |
| **`String`**  | from 1 to 255 symbols       |
| **`bool`**    | true/false                  |
| **`Float`**   | from Float.min to Float.max |
| {% endtab %}  |                             |
| {% endtabs %} |                             |

{% hint style="warning" %}
devtodev supports no more than 300 custom event names in a single project (see [Limits](https://docs.devtodev.com/data-management-and-limits#data-limits)). Events that exceed the limit of custom event names will be discarded. Try to integrate the tracked actions by type to the event name level, and move the characteristic tags to the parameters.

*For example, if you need to track purchasing “Paper” and “Pen” items, then you don’t need to create two events with the names “Paper Purchase” and “Pen Purchase”. Create a “Purchase” event and add an “Item” parameter to it with the appropriate “Paper” or “Pen” value. This way, you can use just one event to track many items.*

For a string parameter, you can use no more than 50,000 unique values ​​for the entire history of events. If the number of unique values exceeds the limit, the parameter gets locked by the system and is discarded from the received data. Therefore, we don’t recommend using highly variable parameters like user IDs or time as string values ​​(moreover, they are automatically added to the event).

We strongly recommend that you do not change the data type passed in the same parameter. If you change the data type in a parameter, it will be duplicated with the same name, which may cause issues while processing reports.
{% endhint %}

## Subscriptions

{% hint style="warning" %}
The described method is available beginning with version 2.1.0!

Tracking of subscriptions is now available for Apple App Store and Google Play only.
{% endhint %}

{% tabs fullWidth="true" %}
{% tab title="App Store (Swift)" %}
{% hint style="info" %}
Please note that in order to track subscriptions, you need to do the following:<br>

1. Call the **`subscriptionPayment`** method (described below)
2. [Configure data transfer from the App Store Connect](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/app-store-subscriptions#step-1.-settings-on-app-store-connect-side)
3. [Configure integration in the devtodev application settings](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/app-store-subscriptions#step-2.-settings-on-devtodev-side)
   {% endhint %}

To track your income from subscriptions, you need to call the following method at the moment of the subscription purchase even if the user signed up for a trial subscription: **`func subscriptionPayment(transaction: SKPaymentTransaction, product: SKProduct).`**

For example:

```swift
extension Purchases: SKPaymentTransactionObserver {
  func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    for transaction in transactions { 
      switch transaction.transactionState {
      case .purchased:
          // Your code ...
          if let product = products?[transaction.payment.productIdentifier] {
             DTDAnalytics.subscriptionPayment(transaction: transaction, product: product)
          }
      case .restored:
          // Your code ...
      case .failed:
          // Your code ...
      default:
          // Your code ...
      }
    }
  }
}
```

Further user actions - renewal, unsubscription, etc. are tracked by using the data received from AppStore in the server-server format. You will need the corresponding setting for it.

Also, if you want to track changes in the status of the subscriptions purchased before devtodev SDK 2.0 integration, you need to transfer your history of previously purchased subscriptions to devtodev.

The SDK monitors the need for historical data to avoid sending out excessive queries to App Store. Use the **`DTDAnalytics.isRestoreTransactionHistoryRequired`**&#x6D;ethod to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns BOOL value.

An example of a purchase history query with verification of the need for it:

```swift
DTDAnalytics.isRestoreTransactionHistoryRequired { isNeedRestore in
  if isNeedRestore {
    DispatchQueue.main.async {
      SKPaymentQueue.default().restoreCompletedTransactions()
    }
  }
}
```

Use the **`DTDAnalytics.subscriptionHistory`** method to transfer the list of previously purchased subscriptions received from App Store.&#x20;

{% hint style="warning" %}
If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.
{% endhint %}

```swift
extension Purchases: SKPaymentTransactionObserver {
  func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
    // Your code ...
    let restoredTransactions = queue.transactions.filter { $0.transactionState == .restored }
    DTDAnalytics.subscriptionHistory(transactions: restoredTransactions)
  }
}
```

{% hint style="info" %}
To recover the purchase history, the user should be logged in with his Apple ID. Be mindful of this before starting the recovering process.
{% endhint %}
{% endtab %}

{% tab title="App Store (Swift) + StoreKit 2" %}
{% hint style="info" %}
Please note that in order to track subscriptions, you need to do the following:<br>

1. Call the **`subscriptionPayment`** method (described below)
2. [Configure data transfer from the App Store Connect](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/app-store-subscriptions#step-1.-settings-on-app-store-connect-side)
3. [Configure integration in the devtodev application settings](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/app-store-subscriptions#step-2.-settings-on-devtodev-side)
   {% endhint %}

To track your income from subscriptions, you need to call the following method at the moment of the subscription purchase even if the user signed up for a trial subscription: **`func subscriptionPayment(transaction: Transaction, product: Product).`**

For example:

```swift
func purchase(_ product: Product) async throws {
  let result = try await product.purchase()

  switch result {
  case let .success(.verified(transaction)):
    // Successful purchase
    await transaction.finish()
    DTDAnalytics.subscriptionPayment(transaction: transaction, product: product)

  case let .success(.unverified(_, error)):
    // Successful purchase but transaction/receipt can't be verified
    // Could be a jailbroken phone
    print(error)

  case .pending:
    // Transaction waiting on SCA (Strong Customer Authentication) or
    // approval from Ask to Buy
    break
  case .userCancelled:
    // Do nothing
    break

  @unknown default:
    break
  }
}
```

Fork with **Transaction**.updates:

```swift
private func listenForTransactions() -> Task<Void, Error> {
  return Task.detached {
      // Iterate through any transactions that don't come from a direct call to `purchase()`.
      for await verificationResult in Transaction.updates {
      guard let case .verified(let transaction) = verificationResult else { return }
      if let revocationDate = transaction.revocationDate {
        // Remove access to the product identified by transaction.productID.
        // Transaction.revocationReason provides details about
        // the revoked transaction.
      } else if let expirationDate = transaction.expirationDate,
                    expirationDate < Date() {
        // Do nothing, this subscription is expired.
        return
      } else if transaction.isUpgraded {
        // Do nothing, there is an active transaction
       // for a higher level of service.
       return
      } else {
       // Provide access to the product identified by
       // transaction.productID.
        if let product = self.products.first(where: { $0.id == transaction.productID }) {
          DTDAnalytics.subscriptionPayment(transaction: transaction, product: product)
        }
      }
    }
  }
}
```

Further user actions - renewal, unsubscription, etc. are tracked by using the data received from AppStore in the server-server format. You will need the corresponding setting for it.

Also, if you want to track changes in the status of the subscriptions purchased before devtodev SDK 2.0 integration, you need to transfer your history of previously purchased subscriptions to devtodev.

The SDK monitors the need for historical data to avoid sending out excessive queries to App Store. Use the **`DTDAnalytics.isRestoreTransactionHistoryRequired`**&#x6D;ethod to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns BOOL value.

An example of a purchase history query with verification of the need for it:

```swift
DTDAnalytics.isRestoreTransactionHistoryRequired { [weak self] flag in
  if flag {
    Task {
      await self?.restoreTransactions()
    }
  }
}
```

Use the **`DTDAnalytics.subscriptionHistory`** method to transfer the list of previously purchased subscriptions received from App Store.&#x20;

{% hint style="warning" %}
If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.
{% endhint %}

```swift
extension Purchases: SKPaymentTransactionObserver {
func restoreTransactions() async {
  var transactions: [Transaction] = []
  for await transaction in Transaction.all {
    if case let .verified(verifiedTransaction) = transaction {
      transactions.append(verifiedTransaction)
    }
  }

  DTDAnalytics.subscriptionHistory(transactions: transactions)
}
```

{% hint style="info" %}
To recover the purchase history, the user should be logged in with his Apple ID. Be mindful of this before starting the recovering process.
{% endhint %}
{% endtab %}

{% tab title="App Store (Objective-C) " %}
{% hint style="info" %}
Please note that in order to track subscriptions, you need to do the following:<br>

1. Call the **`subscriptionPayment`** method (described below)
2. [Configure data transfer from the App Store Connect](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/app-store-subscriptions#step-1.-settings-on-app-store-connect-side)
3. [Configure integration in the devtodev application settings](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/app-store-subscriptions#step-2.-settings-on-devtodev-side)
   {% endhint %}

To track your income from subscriptions, you need to call the following method at the moment of the subscription purchase even if the user signed up for a trial subscription:\
\&#xNAN;**`(void)subscriptionPaymentWithTransaction:(SKPaymentTransaction *  _Nonnull)transaction product:(SKProduct *  _Nonnull)product;`**

For example:

```objectivec
- (void)paymentQueue:(SKPaymentQueue *)queue updatedTransactions:(NSArray *)transactions{
    for(SKPaymentTransaction *transaction in transactions) {
        switch(transaction.transactionState){
            case SKPaymentTransactionStatePurchasing: {
                // Your code ...
                break;
            }

            case SKPaymentTransactionStatePurchased: {
                // Your code ...
                SKProduct *product = [_products objectForKey:transaction.payment.productIdentifier];
                if (product != nil) {
                    [DTDAnalytics subscriptionPaymentWithTransaction:transaction product:product];
                }
                break;
            }

            case SKPaymentTransactionStateRestored: {
                // Your code ...
                break;
            }

            case SKPaymentTransactionStateFailed: {
                // Your code ...
                break;
            }
                
            case SKPaymentTransactionStateDeferred: {
                // Your code ...
                break;
            }
        }
    }
}
```

Further user actions - renewal, unsubscription, etc. are tracked by using the data received from AppStore in the server-server format. You will need the corresponding setting for it.

Also, if you want to track changes in the status of the subscriptions purchased before devtodev SDK 2.0 integration, you need to transfer your history of previously purchased subscriptions to devtodev.

The SDK monitors the need for historical data to avoid sending out excessive queries to App Store. Use the **`(void)isRestoreTransactionHistoryRequiredWithCompletionHandler:( void (^ _Nonnull)(BOOL))completionHandler;`** method to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns BOOL value.

An example of a purchase history query with verification of the need for it:

```objectivec
[DTDAnalytics isRestoreTransactionHistoryRequiredWithCompletionHandler:^(BOOL isNeedRestore){
  if (isNeedRestore == true) {
    dispatch_async(dispatch_get_main_queue(), ^{
      [SKPaymentQueue.defaultQueue restoreCompletedTransactions];
    });
  }
}];
```

Use the **`(void)subscriptionHistoryWithTransactions:(NSArray<SKPaymentTransaction *> * _Nonnull)transactions;`** method to transfer the list of previously purchased subscriptions received from App Store.&#x20;

{% hint style="warning" %}
If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.
{% endhint %}

```objectivec
-(void)paymentQueueRestoreCompletedTransactionsFinished:(SKPaymentQueue *)queue {
  NSMutableArray *restoredTransactions = [NSMutableArray new];
    for (SKPaymentTransaction *transaction in queue.transactions) {
      if (transaction.transactionState == SKPaymentTransactionStateRestored) {
        [restoredTransactions addObject:transaction];
      }
    }
  [DTDAnalytics subscriptionHistoryWithTransactions:restoredTransactions];
}
```

{% hint style="info" %}
To recover the purchase history, the user should be logged in with his Apple ID. Be mindful of this before starting the recovering process.
{% endhint %}
{% endtab %}

{% tab title="Google Play (Kotlin)" %}
{% hint style="info" %}
Please note that in order to track subscriptions, you need to do the following:<br>

1. Call the **`subscriptionPayment`** method (described below)
2. [Configure data transfer from the Google Cloud Platform](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/google-play-subscriptions#settings-in-google-cloud-platform-console)
3. [Configure integration in the devtodev application settings](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/google-play-subscriptions#step-3.-settings-on-devtodev-side)
   {% endhint %}

Before sending a request for subscription from your app, during the creation of **`BillingFlowParams`**, to the **`setObfuscatedAccountId`** function of the **`BillingFlowParams.newBuilder()`** object insert **`obfuscatedAccountId`** obtained from **`DTDAnalytics.getObfuscatedAccountId`**.

{% hint style="warning" %}
Attention! The obfuscated identifier is returned asynchronously, outside of the calling thread!&#x20;
{% endhint %}

Example:

```kotlin
DTDAnalytics.getObfuscatedAccountId { obfuscatedAccountId ->
        val flowParams: BillingFlowParams = BillingFlowParams.newBuilder()
                .setObfuscatedAccountId(obfuscatedAccountId)
                .build()
            
        val result = billingClient.launchBillingFlow(activity, flowParams)
}
```

To track your subscriptions, add this event immediately after the platform confirms that the subscription was approved by the user.

```kotlin
DTDAnalytics.subscriptionPayment(orderId: String,
                                   price: Double,
                               productId: String,
                            currencyCode: String);
```

| **Parameter**      | **Type** | **Restrictions**              | **Description**                                                                                             |
| ------------------ | -------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **`orderId`**      | string   | from 1 to 65 symbols          | A unique transaction identifier.                                                                            |
| **`currencyCode`** | string   | precisely 3 symbols           | Transaction currency ([ISO 4217 standard](https://en.wikipedia.org/wiki/ISO_4217)) e.g. ***USD, EUR*** etc. |
| **`price`**        | double   | from Double.min to Double.max | The item price in the transaction currency.                                                                 |
| **`productId`**    | string   | from 1 to 255 symbols         | Item name. We recommend using a bundle or names in the same language.                                       |

{% hint style="info" %}
How to find the transaction ID in GooglePlay transaction?

Find the INAPP\_PURCHASE\_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP\_PURCHASE\_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.
{% endhint %}

Further user actions - renewal, unsubscription, etc. are tracked by using the data received from Google Play in the server-server format. You will need the corresponding setting for it.

The **`subscriptionHistory`** method is used for matching users with subscribers who purchased their subscriptions before the SDK 2.0 integration. Otherwise, it will be impossible to establish the affiliation when it gets renewed or cancelled.

To get a list of active subscriptions call **`billingClient.queryPurchasesAsync`**. After successfully receiving a response from Google Play Services, pass it to **`DTDAnalytics.subscriptionHistory(purchaseList: List<String>).`**

**`purchaseList: List<String> purchaseList`** - a string containing list of json objects is passed to the **`DTDAnalytics.subscriptionHistory`** method. For the event to run, the json object must contain the following keys:

* **`orderID`** - a unique transaction identifier
* **`productID`** - a unique product identifier

The SDK monitors the need for historical data to avoid sending out excessive queries. Use the **`DTDAnalytics.isRestoreTransactionHistoryRequired`** method to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns a Boolean value.

{% hint style="warning" %}
Attention! **`DTDAnalytics.isRestoreTransactionHistoryRequired`** is returned asynchronously, outside of the calling thread!
{% endhint %}

Example:

```kotlin
DTDAnalytics.isRestoreTransactionHistoryRequired { isNeedRestore ->
  if(isNeedRestore) {
    billingClient.queryPurchasesAsync(BillingClient.SkuType.SUBS) { billingResult, purchaseList ->
      if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
        val purchases = mutableListOf<String>()
        purchaseList.forEach { purchase -> purchases.add(purchase.originalJson) }
        DTDAnalytics.subscriptionHistory(purchases)
      }
    }
  }
}
```

{% hint style="warning" %}
If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.
{% endhint %}
{% endtab %}

{% tab title="Google Play (Java)" %}
{% hint style="info" %}
Please note that in order to track subscriptions, you need to do the following:<br>

1. Call the **`subscriptionPayment`** method (described below)
2. [Configure data transfer from the Google Cloud Platform](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/google-play-subscriptions#settings-in-google-cloud-platform-console)
3. [Configure integration in the devtodev application settings](https://docs.devtodev.com/3rd-party-sources/app-marketplace-data/google-play-subscriptions#step-3.-settings-on-devtodev-side)
   {% endhint %}

Before sending a request for subscription from your app, during the creation of **`BillingFlowParams`**, to the **`setObfuscatedAccountId`** function of the **`BillingFlowParams.newBuilder()`** object insert **`obfuscatedAccountId`** obtained from **DTDAnalytics.INSTANCE.getObfuscatedAccountId**.

{% hint style="warning" %}
Attention! The obfuscated identifier is returned asynchronously, outside of the calling thread!&#x20;
{% endhint %}

Example:

```kotlin
DTDAnalytics.INSTANCE.getObfuscatedAccountId(obfuscatedAccountId -> {
              BillingFlowParams flowParams = BillingFlowParams.newBuilder()
                      .setObfuscatedAccountId(obfuscatedAccountId)
                      .build();
                    
              billingClient.launchBillingFlow(activity, flowParams);
              return null;
      }
);
```

To track your subscriptions, add this event immediately after the platform confirms that the subscription was approved by the user.

```kotlin
DTDAnalytics.INSTANCE.subscriptionPayment(orderId: String, price: Double, productId: String, currencyCode: String);
```

| **Parameter**      | **Type** | **Restrictions**              | **Description**                                                                                             |
| ------------------ | -------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------- |
| **`orderId`**      | string   | from 1 to 65 symbols          | A unique transaction identifier.                                                                            |
| **`currencyCode`** | string   | precisely 3 symbols           | Transaction currency ([ISO 4217 standard](https://en.wikipedia.org/wiki/ISO_4217)) e.g. ***USD, EUR*** etc. |
| **`price`**        | double   | from Double.min to Double.max | The item price in the transaction currency.                                                                 |
| **`productId`**    | string   | from 1 to 255 symbols         | Item name. We recommend using a bundle or names in the same language.                                       |

{% hint style="info" %}
How to find the transaction ID in GooglePlay transaction?

Find the INAPP\_PURCHASE\_DATA object In the JSON fields that are returned in the response data for a purchase order. A unique transaction identifier is the value of orderId property in INAPP\_PURCHASE\_DATA object. If the order is a test purchase made via the In-app Billing Sandbox, orderId property will be empty.
{% endhint %}

Further user actions - renewal, unsubscription, etc. are tracked by using the data received from Google Play in the server-server format. You will need the corresponding setting for it.

The **`subscriptionHistory`** method is used for matching users with subscribers who purchased their subscriptions before the SDK 2.0 integration. Otherwise, it will be impossible to establish the affiliation when it gets renewed or cancelled.

To get a list of active subscriptions call **`billingClient.queryPurchasesAsync`**. After successfully receiving a response from Google Play Services, pass it to **`DTDAnalytics.subscriptionHistory(purchaseList: List<String>).`**

**`purchaseList: List<String> purchaseList`** - a string containing list of json objects is passed to the **`DTDAnalytics.INSTANCE.subscriptionHistory`** method. For the event to run, the json object must contain the following keys:

* **`orderID`** - a unique transaction identifier
* **`productID`** - a unique product identifier

The SDK monitors the need for historical data to avoid sending out excessive queries. Use the **`DTDAnalytics.INSTANCE.isRestoreTransactionHistoryRequired`** method to check whether or not there is a need in sending out the information about the previously purchased subscriptions to devtodev. The method returns a Boolean value.

{% hint style="warning" %}
Attention! **`DTDAnalytics.INSTANCE.isRestoreTransactionHistoryRequired`** is returned asynchronously, outside of the calling thread!
{% endhint %}

Example:

```kotlin
DTDAnalytics.INSTANCE.isRestoreTransactionHistoryRequired(isNeedRestore -> {
    if (isNeedRestore) {
        billingClient.queryPurchasesAsync(BillingClient.ProductType.SUBS, (billingResult, purchaseList) -> {
            if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) {
                ArrayList<String> purchases = new ArrayList<>();
                purchaseList.forEach(purchase ->
                        purchases.add(purchase.getOriginalJson())
                );
                DTDAnalytics.INSTANCE.subscriptionHistory(purchases);
            }
        });
    }
    return null;
});
```

{% hint style="warning" %}
If your project accounts users by user ID (not by device ID) and the device is used by more than one user, you need to filter the transaction history so that it will contain only those transactions that belong to the active user. Otherwise, subscriptions of all device users will be attributed to the user who was the first to launch the app after the integration of subscription tracking.
{% endhint %}
{% endtab %}

{% tab title="App Store+GP (Unity IAPv5)" %}
Integrate the **Subscriptions** module into your project. You can do this by manually importing the *unitypackage*.

## **Integration by importing the unitypackage**

1. Download the latest version of devtodev package from the repository: <https://github.com/devtodev-analytics/Unity-sdk-3.0/releases/latest>
2. Import DTDAnalytics.unitypackage to your project
3. Make sure **Unity IAP v5** is installed via **Package Manager.**&#x20;
4. Import DTDSubscriptions(IAPv5).unitypackage to your project. &#x20;

For the **`DTDSubscriptions`** module to function, you need the **`DTDAnalytics`** and **`Unity IAP`** modules.&#x20;

## Initialization Order&#x20;

Make sure to follow the correct initialization order (see [example](#example) below):&#x20;

1. Initialize Unity Services
2. Initialize DTDAnalytics
3. Create Unity IAP StoreController
4. Subscribe to IAP events
5. Initialize DTDSubscriptions
6. Connect to the store
7. Fetch products
8. Restore history (if required)
9. Fetch purchases

## Initialization

1. Initialize the **`DTDAnalytics`** module (see our [instruction manual](https://docs.devtodev.com/integration/integration-of-sdk-v2/sdk-integration/unity)).&#x20;
2. Create Unity IAP StoreController, subscribe to IAP events and initialize **`DTDSubscriptions`**:&#x20;

```csharp
private async Task InitializePurchasing()
    {
        storeController = UnityIAPServices.StoreController();

        storeController.OnPurchasePending += OnPurchasePending;
        storeController.OnPurchaseFailed += OnPurchaseFailed;
        storeController.OnProductsFetched += OnProductsFetched;
        storeController.OnProductsFetchFailed += OnProductsFetchFailed;
        storeController.OnPurchasesFetched += OnPurchasesFetched;
        storeController.OnStoreDisconnected += OnStoreDisconnected;

        DTDSubscriptions.Initialize(storeController);

        await storeController.Connect();

        storeController.FetchProducts(GetInitialProducts());
    }
```

## Restore purchase history&#x20;

Restore the purchase history to ensure the module functions correctly.&#x20;

Call the **`DTDSubscriptions.History()`** method after successful IAP initialization.&#x20;

{% hint style="info" %}
To avoid excessive restoring of subscription history, use the **`DTDSubscriptions.IsRestoreTransactionHistoryRequired(Action<bool> resultCallback)`** method. If the resultCallback returns true, call the **`DTDSubscriptions.History()`** method.
{% endhint %}

Example:&#x20;

```csharp
private void OnProductsFetched(List<Product> products)
{
    DTDSubscriptions.IsRestoreTransactionHistoryRequired(restoreRequired =>
    {
        if (restoreRequired)
        {
            DTDSubscriptions.History();
        }
    });

    storeController.FetchPurchases();
}
```

## Purchase processing&#x20;

Add a **`DTDSubscriptions.Payment(Product product)`** call to the **`OnPurchasePending`** method.

```csharp
private void OnPurchasePending(PendingOrder pendingOrder)
{
    if (pendingOrder.Info.PurchasedProductInfo
        .FirstOrDefault()?.subscriptionInfo != null)
    {
        DTDSubscriptions.Payment(pendingOrder);
    }

    storeController.ConfirmPurchase(pendingOrder);
}
```

## Example

Full Initialization Example:

```csharp
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.Purchasing;
using Unity.Services.Core;
using Unity.Services.Core.Environments;
using DevToDev.Analytics;
using DevToDev.Subscriptions;

public class IAPManager : MonoBehaviour
{
    private static StoreController storeController;

    private async void Start()
    {
        await InitializeUnityServices();
        InitializeAnalytics();

        if (!IsIAPInitialized())
        {
            await InitializePurchasing();
        }
    }

    private async Task InitializeUnityServices()
    {
        try
        {
            var options = new InitializationOptions()
                .SetEnvironmentName("production");

            await UnityServices.InitializeAsync(options);
        }
        catch (Exception exception)
        {
            Debug.LogError($"Unity Services initialization failed: {exception}");
        }
    }

    private void InitializeAnalytics()
    {
        DTDAnalytics.SetLogLevel(DTDLogLevel.Debug);
        DTDAnalytics.Initialize(string.Empty);
    }

    private bool IsIAPInitialized()
    {
        return storeController != null;
    }

    private async Task InitializePurchasing()
    {
        storeController = UnityIAPServices.StoreController();

        storeController.OnPurchasePending += OnPurchasePending;
        storeController.OnPurchaseFailed += OnPurchaseFailed;
        storeController.OnProductsFetched += OnProductsFetched;
        storeController.OnProductsFetchFailed += OnProductsFetchFailed;
        storeController.OnPurchasesFetched += OnPurchasesFetched;
        storeController.OnStoreDisconnected += OnStoreDisconnected;

        DTDSubscriptions.Initialize(storeController);

        await storeController.Connect();

        storeController.FetchProducts(GetInitialProducts());
    }

    private List<ProductDefinition> GetInitialProducts()
    {
        return new List<ProductDefinition>
        {
            new("fake_consumable_small", ProductType.Consumable),
            new("fake_consumable_large", ProductType.Consumable),
            new("fake_subscription_monthly", ProductType.Subscription),
            new("fake_subscription_yearly", ProductType.Subscription)
        };
    }

    private void OnPurchasePending(PendingOrder pendingOrder)
    {
        if (pendingOrder.Info.PurchasedProductInfo
            .FirstOrDefault()?.subscriptionInfo != null)
        {
            DTDSubscriptions.Payment(pendingOrder);
        }

        storeController.ConfirmPurchase(pendingOrder);
    }

    private void OnPurchaseFailed(FailedOrder failedOrder)
    {
        // Process failed order
    }

    private void OnProductsFetched(List<Product> products)
    {
        DTDSubscriptions.IsRestoreTransactionHistoryRequired(restoreRequired =>
        {
            if (restoreRequired)
            {
                DTDSubscriptions.History();
            }
        });

        storeController.FetchPurchases();
    }

    private void OnProductsFetchFailed(ProductFetchFailed fetchFailed)
    {
        // Process product fetch failure
    }

    private void OnPurchasesFetched(Orders orders)
    {
        // Process confirmed orders
        // Process pending orders
        // Process deferred orders
    }

    private void OnStoreDisconnected(StoreConnectionFailureDescription failureDescription)
    {
        // Process store disconnection
    }

    private void OnDestroy()
    {
        if (storeController == null)
            return;

        storeController.OnPurchasePending -= OnPurchasePending;
        storeController.OnPurchaseFailed -= OnPurchaseFailed;
        storeController.OnProductsFetched -= OnProductsFetched;
        storeController.OnProductsFetchFailed -= OnProductsFetchFailed;
        storeController.OnPurchasesFetched -= OnPurchasesFetched;
        storeController.OnStoreDisconnected -= OnStoreDisconnected;
    }
}
```

{% endtab %}

{% tab title="App Store+GP (Unity IAPv4)" %}
Integrate the **Subscriptions** module into your project. You can do this by manually importing the *unitypackage*.

## **Integration by importing the unitypackage**

1. Download the latest version of devtodev package from the repository: <https://github.com/devtodev-analytics/Unity-sdk-3.0/releases/latest>
2. Import DTDAnalytics.unitypackage to your project
3. Import DTDSubscriptions(IAPv4).unitypackage to your project.

For the **`DTDSubscriptions`** module to function, you need the **`DTDAnalytics`** and Unity IAP modules.&#x20;

You also need to create an ***`AppleTangle`*** file (only for iOS). Open the Unity editor menu and choose ***Window → Unity IAP → Receipt Validation Obfuscator*** (pic. 1).

In case you don’t use the IAP receipt validation, clear the input field under “***2. Paste the key here:***” and click ***Obfuscate Google Play Licence Key*** (pic. 2).

![Pic. 1](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FHpfjKdYAS35vATfHL7t6%2Fimage.png?alt=media\&token=66976987-81dc-48ba-aaa4-c4049eda0300)

![Pic. 2](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FPwRYqFtOo2OZaTrxLGtl%2Fimage.png?alt=media\&token=bd528117-925d-4ef9-a7fa-2c571179945b)

## Initialization

Initialize the **`DTDAnalytics`** module (see our [instruction manual](https://docs.devtodev.com/integration/integration-of-sdk-v2/sdk-integration/unity)).

Add **`DTDSubscriptions.Initialize(IStoreController controller)`** to the [OnInitialized ](https://docs.unity3d.com/Manual/UnityIAPInitialization.html)method.

```csharp
/// <summary>
/// Your IStoreListener implementation of OnInitialized.
/// </summary>
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
   DTDSubscriptions.Initialize(controller);
}
```

## Restore purchase history

Restore purchase history in order for the module to function correctly.

### Google Play

If you use **Google Play**, after successful IAP initialization call the **`DTDSubscriptions.History()`** method.&#x20;

{% hint style="info" %}
To avoid excessive restoring of subscription history, use the **`DTDSubscriptions.IsRestoreTransactionHistoryRequired(Action<bool> resultCallback)`** method. If the resultCallback returns true, call the **`DTDSubscriptions.History()`** method.
{% endhint %}

Example:

```csharp
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
#if UNITY_ANDROID     
     DTDSubscriptions.Initialize(controller);
     DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
     {
        if(b) DTDSubscriptions.History();
     });
#endif
}
```

### App Store

If you use **Apple** **App Store**, first set the **`DTDSubscriptions.IsRestoring`** property to ***true*** (this will filter out unwanted transactions). After [restoring IAP transactions](https://docs.unity3d.com/Manual/UnityIAPRestoringTransactions.html), call the **`DTDSubscriptions.History()`** method. After that, set the **`DTDSubscriptions.IsRestoring propert`**&#x79; to ***false***.

Example:

```csharp
public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
{
    DTDSubscriptions.Initialize(controller);
if UNITY_STANDALONE_OSX || UNITY_IOS
    DTDSubscriptions.IsRestoring = true;
    extensions.GetExtension<IAppleExtensions>().RestoreTransactions(result =>
    {
        if (result)
        {
            DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
            {
                if (b) DTDSubscriptions.History();
            });
        }
        DTDSubscriptions.IsRestoring = false;
    });
#endif
}
```

## Purchase processing

Add a **`DTDSubscriptions.Payment(Product product)`** call to the **`ProcessPurchase`** method.

```csharp
public PurchaseProcessingResult ProcessPurchase (PurchaseEventArgs e)
{
   var product = e.purchasedProduct;
   DTDSubscriptions.Payment(product);
   return PurchaseProcessingResult.Complete;
}
```

## Example

Below you can see an example of the entire script:

```csharp
using DevToDev.Subscriptions;
using UnityEngine;
using UnityEngine.Purchasing;

public class MyIAPManager : IStoreListener
{
    public IStoreController StoreController { get; private set; }

    public void InitializeIAPManager()
    {
        var builder = ConfigurationBuilder.Instance(StandardPurchasingModule.Instance());
        builder.AddProduct("example", ProductType.Subscription);
        UnityPurchasing.Initialize(this, builder);
    }

    /// <summary>
    /// Called when Unity IAP is ready to make purchases.
    /// </summary>
    public void OnInitialized(IStoreController controller, IExtensionProvider extensions)
    {
        StoreController = controller;
        DTDSubscriptions.Initialize(controller);
#if UNITY_ANDROID
        DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
        {
            if (b) DTDSubscriptions.History();
        });
#elif UNITY_STANDALONE_OSX || UNITY_IOS
        DTDSubscriptions.IsRestoring = true;
        extensions.GetExtension<IAppleExtensions>().RestoreTransactions(result =>
        {
            if (result)
            {
                DTDSubscriptions.IsRestoreTransactionHistoryRequired((b) =>
                {
                    if (b) DTDSubscriptions.History();
                });
            }
            DTDSubscriptions.IsRestoring = false;
        });
#endif
    }

    /// <summary>
    /// Called when Unity IAP encounters an unrecoverable initialization error.
    ///
    /// Note that this will not be called if Internet is unavailable; Unity IAP
    /// will attempt initialization until it becomes available.
    /// </summary>
    public void OnInitializeFailed(InitializationFailureReason error)
    {
        Debug.Log($"IAP initialization error {error.ToString()}");
    }

    /// <summary>
    /// Called when a purchase completes.
    ///
    /// May be called at any time after OnInitialized().
    /// </summary>
    public PurchaseProcessingResult ProcessPurchase(PurchaseEventArgs e)
    {
        var product = e.purchasedProduct;
        DTDSubscriptions.Payment(product);
        return PurchaseProcessingResult.Complete;
    }

    /// <summary>
    /// Called when a purchase fails.
    /// </summary>
    public void OnPurchaseFailed(Product i, PurchaseFailureReason p)
    {
        Debug.Log(p.ToString());
    }
}


```

{% endtab %}
{% endtabs %}

## **Onboarding (tutorial)**

The event allows you to track tutorial completion and identify the stages where you lose new users.

We recommend tracking the starting point (value  ***-1***) before beginning the first tutorial stage, then passing the counting number of every completed stage after its completion (integers larger than 0), and at the end, marking the moment of the last tutorial stage completion (value  ***-2***).

If your app has an option of skipping the tutorial and the user has used it, then it’s necessary to send a refusal value (value ***0***) only.

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}

```swift
DTDAnalytics.tutorial(step: 1)
```

The method takes on the step value with an integer type.

| Value          | Meaning                                          |
| -------------- | ------------------------------------------------ |
| ***`0`***      | The user skipped the tutorial                    |
| ***`-1`***     | The value defines the beginning of the tutorial  |
| ***`1..int`*** | Counting number of completed tutorial stage      |
| ***`-2`***     | The value defines the completion of the tutorial |
| {% endtab %}   |                                                  |

{% tab title="iOS+macOS (Objective-C)" %}

```objectivec
[DTDAnalytics tutorialStep:1];
```

The method takes on the step value with an integer type.

| Value          | Meaning                                          |
| -------------- | ------------------------------------------------ |
| ***`0`***      | The user skipped the tutorial                    |
| ***`-1`***     | The value defines the beginning of the tutorial  |
| ***`1..int`*** | Counting number of completed tutorial stage      |
| ***`-2`***     | The value defines the completion of the tutorial |
| {% endtab %}   |                                                  |

{% tab title="Android (Kotlin)" %}

```kotlin
DTDAnalytics.tutorial(step = 1)
```

The method takes on the step value with an integer type.

<table><thead><tr><th width="419.04224441097614">Value</th><th>Meaning</th></tr></thead><tbody><tr><td><em><strong><code>0</code></strong></em></td><td>The user skipped the tutorial</td></tr><tr><td><em><strong><code>-1</code></strong></em></td><td>The value defines the beginning of the tutorial</td></tr><tr><td><em><strong><code>1..int</code></strong></em></td><td>Counting number of completed tutorial stage</td></tr><tr><td><em><strong><code>-2</code></strong></em></td><td>The value defines the completion of the tutorial</td></tr></tbody></table>
{% endtab %}

{% tab title="Android (Java)" %}

```java
DTDAnalytics.INSTANCE.tutorial(1);
```

The method takes on the step value with an integer type.

<table><thead><tr><th width="419.04224441097614">Value</th><th>Meaning</th></tr></thead><tbody><tr><td><em><strong><code>0</code></strong></em></td><td>The user skipped the tutorial</td></tr><tr><td><em><strong><code>-1</code></strong></em></td><td>The value defines the beginning of the tutorial</td></tr><tr><td><em><strong><code>1..int</code></strong></em></td><td>Counting number of completed tutorial stage</td></tr><tr><td><em><strong><code>-2</code></strong></em></td><td>The value defines the completion of the tutorial</td></tr></tbody></table>
{% endtab %}

{% tab title=".NET Native + UWP" %}

```csharp
DTDAnalytics.Tutorial(1);
```

The method takes on the step value with an integer type.

| Value          | Meaning                                          |
| -------------- | ------------------------------------------------ |
| ***`0`***      | The user skipped the tutorial                    |
| ***`-1`***     | The value defines the beginning of the tutorial  |
| ***`1..int`*** | Counting number of completed tutorial stage      |
| ***`-2`***     | The value defines the completion of the tutorial |
| {% endtab %}   |                                                  |

{% tab title="Unity" %}

```csharp
DTDAnalytics.Tutorial(1);
```

The method takes on the step value with an int base type.

| Value          | Meaning                                          |
| -------------- | ------------------------------------------------ |
| ***`0`***      | The user skipped the tutorial                    |
| ***`-1`***     | The value defines the beginning of the tutorial  |
| ***`1..int`*** | Counting number of completed tutorial stage      |
| ***`-2`***     | The value defines the completion of the tutorial |
| {% endtab %}   |                                                  |

{% tab title="Web" %}

```javascript
analytics.tutorial(1)
```

The method takes on the step value with an integer type.

| Value          | Meaning                                          |
| -------------- | ------------------------------------------------ |
| ***`0`***      | The user skipped the tutorial                    |
| ***`-1`***     | The value defines the beginning of the tutorial  |
| ***`1..int`*** | Counting number of completed tutorial stage      |
| ***`-2`***     | The value defines the completion of the tutorial |
| {% endtab %}   |                                                  |

{% tab title="Unreal" %}
![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FDB4Q0W68vTgw5gqo7dzQ%2Fimage.png?alt=media\&token=509e7c0a-9205-4f30-8439-90f30aa1af3a)

| Parameter  | Type  | Restrictions                 | Description   |
| ---------- | ----- | ---------------------------- | ------------- |
| **`step`** | int32 | From 1 to int32.MaxValue - 1 | Tutorial step |

```cpp
UDTDAnalyticsBPLibrary::Tutorial(1);
```

The method takes on the step value with an int32 base type.

| Value            | Meaning                                          |
| ---------------- | ------------------------------------------------ |
| ***`0`***        | The user skipped the tutorial                    |
| ***`-1`***       | The value defines the beginning of the tutorial  |
| ***`1..int32`*** | Counting number of completed tutorial stage      |
| ***`-2`***       | The value defines the completion of the tutorial |
| {% endtab %}     |                                                  |

{% tab title="Godot" %}

```gdscript
DTDAnalytics.Tutorial(1)
```

The method takes on the step value with an integer type.

| Value          | Meaning                                          |
| -------------- | ------------------------------------------------ |
| ***`0`***      | The user skipped the tutorial                    |
| ***`-1`***     | The value defines the beginning of the tutorial  |
| ***`1..int`*** | Counting number of completed tutorial stage      |
| ***`-2`***     | The value defines the completion of the tutorial |
| {% endtab %}   |                                                  |
| {% endtabs %}  |                                                  |

## Level up

This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the [application settings](https://docs.devtodev.com/getting-started/adding-an-app-to-the-space). In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → [General settings](https://docs.devtodev.com/reports-and-functionality/project-related-reports-and-fuctionality/settings#general-settings).

The event allows you to analyze the distribution of players over different game levels, monitor the in-game currency balance by levels. You can find more information about the right moment to use **`LevelUp`** event [here](https://docs.devtodev.com/expert-tips/what-to-track#if-users-in-your-project-become-more-experienced-and-raise-their-level).&#x20;

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```swift
DTDAnalytics.levelUp(level: 2)
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

```swift
let balance: [String: Int] = ["Currency name 1": 100, "Currency name 2": 10]
DTDAnalytics.levelUp(level: 2, balances: balance)
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type          | Restrictions                                                                 | Description                                         |
| ---------------- | ------------- | ---------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***    | int           | From 1 to Int32.max - 1                                                      | Level reached                                       |
| ***`balances`*** | \[String:Int] | <p>String - from 1 to 24 symbols</p><p>Int - from Int64.min to int64.max</p> | Resources’ names and number at the time of level up |
| {% endtab %}     |               |                                                                              |                                                     |

{% tab title="iOS+macOS (Objective-C)" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```objectivec
[DTDAnalytics levelUp:2];
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

```objectivec
NSDictionary *balance = @{@"Currency name 1": @100, @"Currency name 2": @10};
[DTDAnalytics levelUp:2 withBalances:balance];
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type                                   | Restrictions                                                                 | Description                                         |
| ---------------- | -------------------------------------- | ---------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***    | NSInteger                              | From 1 to Int32.max - 1                                                      | Level reached                                       |
| ***`balances`*** | NSDictionary\<NSString \*,NSNumber \*> | <p>String - from 1 to 24 symbols</p><p>Int - from Int64.min to int64.max</p> | Resources’ names and number at the time of level up |
| {% endtab %}     |                                        |                                                                              |                                                     |

{% tab title="Android (Kotlin)" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```kotlin
DTDAnalytics.levelUp(level = 2)
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:

```kotlin
val balances = mapOf("Currency name 1" to 100L, "Currency name 2" to 10L)
DTDAnalytics.levelUp(
    level = 2, 
    balance = balances
)
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type              | Restrictions                                                                | Description                                         |
| ---------------- | ----------------- | --------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***    | int               | From 1 to Int.max - 1                                                       | Level reached                                       |
| ***`balances`*** | Map\<String,Long> | <p>String - from 1 to 24 symbols</p><p>Long - from Long.min to Long.max</p> | Resources’ names and number at the time of level up |
| {% endtab %}     |                   |                                                                             |                                                     |

{% tab title="Android (Java)" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```java
DTDAnalytics.INSTANCE.levelUp(2);
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:&#x20;

```java
Map<String, Long> balances = new HashMap<>();
balances.put("Currency name 1", 100L);
balances.put("Currency name 2", 10L);
DTDAnalytics.INSTANCE.levelUp(2, balances);
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type              | Restrictions                                                                | Description                                         |
| ---------------- | ----------------- | --------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***    | int               | From 1 to Int.max - 1                                                       | Level reached                                       |
| ***`balances`*** | Map\<String,Long> | <p>String - from 1 to 24 symbols</p><p>Long - from Long.min to Long.max</p> | Resources’ names and number at the time of level up |
| {% endtab %}     |                   |                                                                             |                                                     |

{% tab title=".NET Native + UWP" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```csharp
DTDAnalytics.LevelUp(2);
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:&#x20;

```csharp
var balance = new Dictionary<string, long>();
balance.Add("Currency name 1", 100);
balance.Add("Currency name 2", 200);
DTDAnalytics.LevelUp(2, balance);
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type            | Restrictions                                                                          | Description                                         |
| ---------------- | --------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***    | int             | From 1 to int.MaxValue - 1                                                            | Level reached                                       |
| ***`balances`*** | \[string: long] | <p>String - from 1 to 24 symbols</p><p>Long - from long.MinValue to long.MaxValue</p> | Resources’ names and number at the time of level up |
| {% endtab %}     |                 |                                                                                       |                                                     |

{% tab title="Unity" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the ***`level`*** parameter.

```csharp
DTDAnalytics.LevelUp(level: 2)
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:&#x20;

```csharp
var balance = new Dictionary<string, long>();
balance.Add("Currency name 1", 100);
balance.Add("Currency name 2", 200);
DTDAnalytics.LevelUp(level: 2, balances: balance);
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type            | Restrictions                                                                              | Description                                         |
| ---------------- | --------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***    | int             | From 1 to int32.MaxValue - 1                                                              | Level reached                                       |
| ***`balances`*** | \[string: long] | <p>String - from 1 to 24 symbols</p><p>Long - from long64.MinValue to long64.MaxValue</p> | Resources’ names and number at the time of level up |
| {% endtab %}     |                 |                                                                                           |                                                     |

{% tab title="Web" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```javascript
analytics.levelUp(2)
```

You can send and track the following data along with the level values: an average amount of the in-game currency by the end of the level, user spendings on the level, and amounts of purchased or earned in-game currency/resources.\
Unfortunately, Web SDK doesn’t allow to automatically calculate spending and receiving of the in-game currency/resources while users are passing the level (data accumulation on the Web might be inaccurate as users might utilize multiple browsers and devices, as well as erase local browser data).&#x20;

```javascript
const balance = {
                "Currency name 1" : 100,
                "Currency name 2" : 200
}
const spent = {
                "Currency name 2" : 1
}
const earned = {
                                
                "Currency name 1" : 5
}
const bought = {
                "Currency name 1" : 50,
                "Currency name 2" : 30
}
analytics.levelUp(2, balance, spent,  earned, bought)
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter        | Type            | Restrictions                                                                          | Description                                             |
| ---------------- | --------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------- |
| ***`level`***    | int             | From 1 to int.MaxValue - 1                                                            | Level reached                                           |
| ***`balances`*** | \[string: long] | <p>String - from 1 to 24 symbols</p><p>Long - from long.MinValue to long.MaxValue</p> | Resources’ names and number at the time of level up     |
| ***`spent`***    | \[string: long] | <p>String - from 1 to 24 symbols<br>Long - from 0 to Number.MAX\_SAFE\_INTEGER</p>    | Game currency amount spent during the level. Optional.  |
| ***`earned`***   | \[string: long] | <p>String - from 1 to 24 symbols<br>Long - from 0 to Number.MAX\_SAFE\_INTEGER</p>    | Game currency earned during the level. Optional.        |
| ***`bought`***   | \[string: long] | <p>String - from 1 to 24 symbols<br>Long - from 0 to Number.MAX\_SAFE\_INTEGER</p>    | Game currency amount bought during the level. Optional. |
| {% endtab %}     |                 |                                                                                       |                                                         |

{% tab title="Unreal" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the ***`level`*** parameter.

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FNQ7vMDtSpZ3v3psJpZek%2Fimage.png?alt=media\&token=cb2c6a1e-74db-4000-abd2-32fe34852f34)

| Parameter     | Type  | Restrictions                 | Description   |
| ------------- | ----- | ---------------------------- | ------------- |
| ***`level`*** | int32 | From 1 to int32.MaxValue - 1 | Level reached |

```cpp
UDTDAnalyticsBPLibrary::LevelUp(2);
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:&#x20;

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FXMVSVu5XF7gMLYt2RMmc%2Fimage.png?alt=media\&token=e6dddbde-accf-46db-ba68-b887461d8103)

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

| Parameter       | Type                  | Restrictions                                                                              | Description                                         |
| --------------- | --------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------- |
| ***`level`***   | int32                 | From 1 to int32.MaxValue - 1                                                              | Level reached                                       |
| ***`balance`*** | TMap\<FString, int64> | <p>FString - from 1 to 24 symbols</p><p>int64 - from int64.MinValue to int64.MaxValue</p> | Resources’ names and number at the time of level up |

```cpp
TMap<FString, int64> balance;
balance.Add("CurrencyName", 123);
UDTDAnalyticsBPLibrary::LevelUpWithBalance(2, balance);
```

{% endtab %}

{% tab title="Godot" %}
The event should be dispatched right after the level-up. The number of the level reached is passed to the **`level`** parameter.

```gdscript
DTDAnalytics.LevelUp(2)
```

To monitor the average account balance of in-game currency by the end of each level, dispatch in-game currencies (resources) names and their amounts to the method signature:&#x20;

```gdscript
var balances = GDDTDInt64Resources.new()
balances.AddValue("level_resource_1", 6000)
balances.AddValue("level_resource_2", 95001000)
DTDAnalytics.LevelUpWithBalance(2, balances)
```

{% hint style="warning" %}
Attention! The number of tracked in-game currencies or resources (their unique names) should not exceed 50 at all times. See [Limits](https://docs.devtodev.com/data-management-and-limits).
{% endhint %}

<table><thead><tr><th width="148">Parameter</th><th width="129">Type</th><th width="201">Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>level</code></strong></em></td><td>int</td><td>From 1 to Int32.max - 1</td><td>Level reached</td></tr><tr><td><em><strong><code>balances</code></strong></em></td><td>GDDTDInt64Resources</td><td><p>String - from 1 to 24 symbols</p><p>Int - from Int64.min to int64.max</p></td><td>Resources’ names and number at the time of level up</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

## Current Balance

This event is for games only. It is worthwhile to integrate this event into a game-type project, as specified in the [application settings](https://docs.devtodev.com/getting-started/adding-an-app-to-the-space). In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → [General settings](https://docs.devtodev.com/reports-and-functionality/project-related-reports-and-fuctionality/settings#general-settings).

To track the average balance of in-game currency disregarding the level up event, pass the list of in-game currency (resource) names and their amount to the method signature:

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}

```swift
let balance: [String: Int] = ["Currency name 1": 100, "Currency name 2": 10]
DTDAnalytics.currentBalance(balance: balance)
```

{% endtab %}

{% tab title="iOS+macOS (Objective-C)" %}

```objectivec
 NSDictionary *balance = @{@"Currency name 1": @100, 
                           @"Currency name 2": @10};
 [DTDAnalytics currentBalanceWithBalance:balance];
```

{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
val balance = mapOf("Currency name 1" to 100L, 
                    "Currency name 2" to 10L)
DTDAnalytics.currentBalance(
    balance = balance
)
```

{% endtab %}

{% tab title="Android (Java)" %}

```java
Map<String, Long> balance = new HashMap<>();
balance.put("Currency name 1", 100L);
balance.put("Currency name 2", 10L);
DTDAnalytics.INSTANCE.currentBalance(balance);
```

{% endtab %}

{% tab title=".NET Native + UWP" %}

```csharp
var balance = new Dictionary<string, long>();
balance.Add("Currency name 1", 100);
balance.Add("Currency name 2", 200);
DTDAnalytics.CurrentBalance(balance);
```

{% endtab %}

{% tab title="Unity" %}

```csharp
var balance = new Dictionary<string, long>();
balance.Add("Currency name 1", 100);
balance.Add("Currency name 2", 200);
DTDAnalytics.CurrentBalance(balance);
```

{% endtab %}

{% tab title="Web" %}

```csharp
const balance = {
                "Currency name 1" : 100,
                "Currency name 2" : 10
}
analytics.currentBalance(balance);
```

{% endtab %}

{% tab title="Unreal" %}
![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FiOfGkNeIposGPWFygUnS%2Fimage.png?alt=media\&token=657e9a5f-73a1-4b7f-b75a-8a714edb3487)

| Parameter       | Type                  | Restrictions                                                                              | Description                 |
| --------------- | --------------------- | ----------------------------------------------------------------------------------------- | --------------------------- |
| ***`balance`*** | TMap\<FString, int64> | <p>FString - from 1 to 24 symbols</p><p>int64 - from int64.MinValue to int64.MaxValue</p> | Resources’ names and number |

```cpp
TMap<FString, int64> balance;
balance.Add("CurrencyName", 123);
UDTDAnalyticsBPLibrary::CurrentBalance(balance);
```

{% endtab %}

{% tab title="Godot" %}

```gdscript
var balance = GDDTDInt64Resources.new()
balance.AddValue("current_balance_res_1", 222)
balance.AddValue("current_balance_res_2", 1500)
DTDAnalytics.CurrentBalance(balance)
```

{% endtab %}
{% endtabs %}

## Currency Accrual

This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the [application settings](https://docs.devtodev.com/getting-started/adding-an-app-to-the-space). In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → [General settings](https://docs.devtodev.com/reports-and-functionality/project-related-reports-and-fuctionality/settings#general-settings).

You need to dispatch the event after every game account balance refill if you want to track the average in-game currency amount acquired or earned by the players for a certain timeframe or during a level playthrough.

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}

```swift
DTDAnalytics.currencyAccrual(currencyName: "Currency name 1", 
                             currencyAmount: 100, 
                             source: "Source name", 
                             accrualType: .earned)
```

<table data-full-width="false"><thead><tr><th>Parameter</th><th>Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>currencyName</code></strong></em></td><td>string</td><td>from 1 to 24 symbols</td><td>In-game currency/resource name</td></tr><tr><td><em><strong><code>currencyAmount</code></strong></em></td><td>int</td><td>from 1 to Int32.max</td><td>Amount of currency in circulation</td></tr><tr><td><em><strong><code>source</code></strong></em></td><td>string</td><td>from 1 to 23 symbols</td><td>The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency.</td></tr><tr><td><em><strong><code>accrualType</code></strong></em></td><td>DTDAccrualType (enum)</td><td></td><td>The currency/resource source type. The player can either gain resources during the game (<strong><code>earned</code></strong>) or purchase them for money (<strong><code>bought</code></strong>)</td></tr></tbody></table>

**`acrualType`** can receive one of the following values:

```swift
public enum DTDAccrualType: Int {
    case earned = 0
    case bought = 1
}
```

{% endtab %}

{% tab title="iOS+macOS (Objective-C)" %}

```objectivec
[DTDAnalytics currencyName:@"Currency name 1"
              currencyAmount:100
              source:@"Source name"
              accrualType:DTDAccrualTypeEarned];
```

| Parameter              | Type                  | Restrictions         | Description                                                                                                                                                                                                                                          |
| ---------------------- | --------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`***   | NSString              | from 1 to 24 symbols | In-game currency/resource name                                                                                                                                                                                                                       |
| ***`currencyAmount`*** | NSInteger             | from 1 to Int32.max  | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***         | NSString              | from 1 to 23 symbols | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***    | DTDAccrualType (enum) |                      | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

**`acrualType`** can receive one of the following values:

```objectivec
typedef enum DTDAccrualType: NSUInteger {
DTDAccrualTypeEarned = 0,
DTDAccrualTypeBought = 1,
} DTDAccrualType;
```

{% endtab %}

{% tab title="Android (Kotlin)" %}

```kotlin
DTDAnalytics.currencyAccrual(
    currencyName = "Currency name 1",
    currencyAmount = 100,
    source = "Source name",
    DTDAccrualType = DTDAccrualType.Earned
)
```

| Parameter            | Type                  | Restrictions         | Description                                                                                                                                                                                                                                          |
| -------------------- | --------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`*** | string                | from 1 to 24 symbols | In-game currency/resource name                                                                                                                                                                                                                       |
| **`currencyAmount`** | int                   | from 1 to Int.max    | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***       | string                | from 1 to 23 symbols | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***  | DTDAccrualType (enum) |                      | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

**`accrualType`** can receive one of the following values:

```kotlin
enum class DTDAccrualType(val value: Long) {
    Earned(0L),
    Bought(1L);
}
```

{% endtab %}

{% tab title="Android (Java)" %}

```java
DTDAnalytics.INSTANCE.currencyAccrual(
        "Currency name 1",
        100, 
        "Source name",
        DTDAccrualType.Earned
);
```

| Parameter            | Type                  | Restrictions         | Description                                                                                                                                                                                                                                          |
| -------------------- | --------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`*** | string                | from 1 to 24 symbols | In-game currency/resource name                                                                                                                                                                                                                       |
| **`currencyAmount`** | int                   | from 1 to Int.max    | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***       | string                | from 1 to 23 symbols | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***  | DTDAccrualType (enum) |                      | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

**`accrualType`** can receive one of the following values:

```java
public final enum class DTDAccrualType {
    Earned;
    Bought;
}
```

{% endtab %}

{% tab title=".NET Native + UWP" %}

```csharp
DTDAnalytics.CurrencyAccrual(
    currencyName: "Currency name 1",
    currencyAmount: 100,
    source: "Source name",
    accrualType: DTDAccrualType.Earned); 

```

| Parameter              | Type                  | Restrictions           | Description                                                                                                                                                                                                                                          |
| ---------------------- | --------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`***   | string                | from 1 to 24 symbols   | In-game currency/resource name                                                                                                                                                                                                                       |
| ***`currencyAmount`*** | int                   | from 1 to Int.MaxValue | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***         | string                | from 1 to 23 symbols   | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***    | DTDAccrualType (enum) |                        | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

**`AccrualType`** can receive one of the following values:

```csharp
enum DTDAccrualType : long
{
    Earned = 0L,
    Bought = 1L
}
```

{% endtab %}

{% tab title="Unity" %}

```csharp
DTDAnalytics.CurrencyAccrual(currencyName: "Currency name 1", 
                             currencyAmount: 100, 
                             source: "Source name", 
                             accrualType: DTDAccrualType.Earned)

```

| Parameter              | Type                  | Restrictions             | Description                                                                                                                                                                                                                                          |
| ---------------------- | --------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`***   | string                | from 1 to 24 symbols     | In-game currency/resource name                                                                                                                                                                                                                       |
| ***`currencyAmount`*** | int                   | from 1 to Int32.MaxValue | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***         | string                | from 1 to 23 symbols     | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***    | DTDAccrualType (enum) |                          | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

**`AccrualType`** can receive one of the following values:

```csharp
public enum DTDAccrualType
{
  Earned = 0,
  Bought = 1
}
```

{% endtab %}

{% tab title="Web" %}

```javascript
analytics.currencyAccrual(
                                currencyName,
                                currencyAmount,
                                source,
                                accrualType)
```

| Parameter              | Type   | Restrictions         | Description                                                                                                                                                                                                                                          |
| ---------------------- | ------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`***   | string | from 1 to 24 symbols | In-game currency/resource name                                                                                                                                                                                                                       |
| ***`currencyAmount`*** | int    | from 1 to Int.max    | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***         | string | from 1 to 23 symbols | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***    | int    |                      | The currency/resource source type. The player can either gain resources during the game (***`0`***) or purchase them for money (***`1`***)                                                                                                           |

Example:

```javascript
analytics.currencyAccrual(
                                "Currency name 1",
                                100,
                                "Source name",
                                0)
```

{% endtab %}

{% tab title="Unreal" %}
![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FAVHQhg6vhXkwqVStD3Nm%2Fimage.png?alt=media\&token=2b514870-8de2-4730-9eb6-b98429cd42f3)

| Parameter              | Type            | Restrictions             | Description                                                                                                                                                                                                                                          |
| ---------------------- | --------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`***   | FString         | from 1 to 24 symbols     | In-game currency/resource name                                                                                                                                                                                                                       |
| ***`currencyAmount`*** | int32           | from 1 to Int32.MaxValue | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***         | FString         | from 1 to 23 symbols     | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***    | EDTDAccrualType |                          | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

```cpp
UDTDAnalyticsBPLibrary::CurrencyAccrual("CurrencyName", 12, "Source", EDTDAccrualType::Bought);
```

{% endtab %}

{% tab title="Godot" %}

```gdscript
DTDAnalytics.CurrencyAccrual("currencyName_1", 500, "store", GDDTDAccrualType.Bought)
DTDAnalytics.CurrencyAccrual("currencyName_2", 10, "market", GDDTDAccrualType.Earned)
```

| Parameter              | Type                    | Restrictions         | Description                                                                                                                                                                                                                                          |
| ---------------------- | ----------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`currencyName`***   | String                  | from 1 to 24 symbols | In-game currency/resource name                                                                                                                                                                                                                       |
| ***`currencyAmount`*** | int                     | from 1 to Int32.max  | Amount of currency in circulation                                                                                                                                                                                                                    |
| ***`source`***         | String                  | from 1 to 23 symbols | The sources of currency/resources. It can be used for breaking down income by its sources. For example, a city builder game may have some: “Rent” for the profit received from rental property, or “Bank” if the player has purchased some currency. |
| ***`accrualType`***    | GDDTDAccrualType (enum) |                      | The currency/resource source type. The player can either gain resources during the game (**`earned`**) or purchase them for money (**`bought`**)                                                                                                     |

**`acrualType`** can receive one of the following values:

```swift
enum  AccrualType:
    Earned = 0
    Bought = 1
```

{% endtab %}
{% endtabs %}

## Virtual Currency Payment

This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the [application settings](https://docs.devtodev.com/getting-started/adding-an-app-to-the-space). In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → [General settings](https://docs.devtodev.com/reports-and-functionality/project-related-reports-and-fuctionality/settings#general-settings).

Pass this event after every purchase if you want to track in-game currency spends and items’ popularity. You can apply this event to both games and any apps with virtual currency.

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}

```kotlin
DTDAnalytics.virtualCurrencyPayment(purchaseId: "Purchase ID",
                                    purchaseType: "Purchase type",
                                    purchaseAmount: 100,
                                    purchasePrice: 10,
                                    purchaseCurrency: "Purchase currency")
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```kotlin
let resources: [String: Int] = ["Purchase currency name 1": 100, 
                                "Purchase currency name 2": 10]
DTDAnalytics.virtualCurrencyPayment(purchaseId: "Purchase ID",
                                    purchaseType: "Purchase Type",
                                    purchaseAmount: 100,
                                    resources: resources)
```

| Parameter                | Type   | Restrictions         | Description                                                                                                                              |
| ------------------------ | ------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | string | from 1 to 32 symbols | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | string | from 1 to 96 symbols | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to Int32.max  | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | string | from 1 to 24 symbols | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | int    | from 1 to Int32.max  | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                      |                                                                                                                                          |

{% tab title="iOS+macOS (Objective-C)" %}

```objectivec
[DTDAnalytics virtualCurrencyPaymentWithPurchaseId:@"Purchase ID"
                                      purchaseType:@"Purchase type"
                                    purchaseAmount:100
                                     purchasePrice:10
                                  purchaseCurrency:@"Purchase currency"];
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```objectivec
NSDictionary *resources = @{@"Purchase currency name 1": @100,
                            @"Purchase currency name 2": @10};
[DTDAnalytics virtualCurrencyPaymentWithPurchaseId:@"Purchase ID"
                                      purchaseType:@"Purchase Type"
                                    purchaseAmount:100
                                         resources:resources];
```

| Parameter                | Type      | Restrictions         | Description                                                                                                                              |
| ------------------------ | --------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | NSString  | from 1 to 32 symbols | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | NSString  | from 1 to 96 symbols | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | NSInteger | from 1 to Int32.max  | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | NSString  | from 1 to 24 symbols | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | NSInteger | from 1 to Int32.max  | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |           |                      |                                                                                                                                          |

{% tab title="Android (Kotlin)" %}

```kotlin
DTDAnalytics.virtualCurrencyPayment(
    purchaseId = "Purchase ID",
    purchaseType = "Purchase type",
    purchaseAmount = 100,
    purchasePrice = 10,
    purchaseCurrency = "Purchase currency"
)
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```kotlin
val resources = mapOf(
    "Purchase currency name 1" to 100,
    "purchase Currency name 2" to 10
)
ot
DTDAnalytics.virtualCurrencyPayment(
    purchaseId = "Purchase ID",
    purchaseType = "Purchase Type",
    purchaseAmount = 100,
    map = resources
)
```

| Parameter                | Type   | Restrictions         | Description                                                                                                                              |
| ------------------------ | ------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | string | from 1 to 32 symbols | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | string | from 1 to 96 symbols | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to Int.max    | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | string | from 1 to 24 symbols | The name of a currency used for the purchase.                                                                                            |
| **`purchasePrice`**      | int    | from 1 to Int.max    | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                      |                                                                                                                                          |

{% tab title="Android (Java)" %}

```java
DTDAnalytics.INSTANCE.virtualCurrencyPayment(
        "Purchase ID",
        "Purchase type",
        100,
        10,
        "Purchase currency" 
);
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```java
Map<String, Integer> resources = new HashMap<>();
resources.put("Purchase currency name 1", 100);
resources.put("Purchase currency name 2", 10);
DTDAnalytics.INSTANCE.virtualCurrencyPayment(
        "Purchase ID",
        "Purchase Type",
        100,
        resources
);
```

| Parameter                | Type   | Restrictions         | Description                                                                                                                              |
| ------------------------ | ------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | string | from 1 to 32 symbols | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | string | from 1 to 96 symbols | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to Int.max    | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | string | from 1 to 24 symbols | The name of a currency used for the purchase.                                                                                            |
| **`purchasePrice`**      | int    | from 1 to Int.max    | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                      |                                                                                                                                          |

{% tab title=".NET Native + UWP" %}

```csharp
DTDAnalytics.VirtualCurrencyPayment(
    purchaseId: "Purchase ID",
    purchaseType: "Purchase type",
    purchaseAmount: 100,
    purchasePrice: 10,
    purchaseCurrency: "Purchase currency");
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```csharp
var resources = new Dictionary<string, int>
{
    ["Purchase currency name 1"] = 100,
    ["purchase Currency name 2"] = 10
};
DTDAnalytics.VirtualCurrencyPayment(
    purchaseId: "Purchase ID",
    purchaseType: "Purchase Type",
    purchaseAmount: 100,
    resources: resources);
```

| Parameter                | Type   | Restrictions           | Description                                                                                                                              |
| ------------------------ | ------ | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | string | from 1 to 32 symbols   | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | string | from 1 to 96 symbols   | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to int.MaxValue | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | string | from 1 to 24 symbols   | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | int    | from 1 to int.MaxValue | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                        |                                                                                                                                          |

{% tab title="Unity" %}

```csharp
DTDAnalytics.VirtualCurrencyPayment(purchaseId: "Purchase ID",
                                    purchaseType: "Purchase type",
                                    purchaseAmount: 100,
                                    purchasePrice: 10,
                                    purchaseCurrency: "Purchase currency")
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```csharp
var resources = new Dictionary<string, int>
{
    ["Purchase currency name 1"] = 100,
    ["purchase Currency name 2"] = 10
};
DTDAnalytics.VirtualCurrencyPayment(
    purchaseId: "Purchase ID",
    purchaseType: "Purchase Type",
    purchaseAmount: 100,
    resources: resources);
```

| Parameter                | Type   | Restrictions             | Description                                                                                                                              |
| ------------------------ | ------ | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | string | from 1 to 32 symbols     | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | string | from 1 to 96 symbols     | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to Int32.MaxValue | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | string | from 1 to 24 symbols     | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | int    | from 1 to Int32.MaxValue | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                          |                                                                                                                                          |

{% tab title="Web" %}

```javascript
analytics.virtualCurrencyPayment(
                                purchaseId,
                                purchaseType,
                                purchaseAmount, 
                                purchasePrice,
                                purchaseCurrency)
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```javascript
const resources = {
                "Purchase currency name 1": 100,
                "purchase Currency name 2": 10
};
analytics.virtualCurrencyPayment(
                "Purchase ID",
                "Purchase type",
                100, 
                resources)
```

| Parameter                | Type   | Restrictions                        | Description                                                                                                                              |
| ------------------------ | ------ | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | string | from 1 to 32 symbols                | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | string | from 1 to 96 symbols                | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to Number.MAX\_SAFE\_INTEGER | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | string | from 1 to 24 symbols                | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | int    | from 1 to Number.MAX\_SAFE\_INTEGER | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                                     |                                                                                                                                          |

{% tab title="Unreal" %}

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FYcWDMRzitI8gWsZGjQ0e%2Fimage.png?alt=media\&token=f7db7e2c-f746-4ae0-96b9-793ef0061ce7)

| Parameter                | Type    | Restrictions             | Description                                                                                                                              |
| ------------------------ | ------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | FString | from 1 to 32 symbols     | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | FString | from 1 to 96 symbols     | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int32   | from 1 to Int32.MaxValue | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | FString | from 1 to 24 symbols     | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | int32   | from 1 to Int32.MaxValue | The price of the purchased item in the specified in-game currency.                                                                       |

```cpp
UDTDAnalyticsBPLibrary::VirtualCurrencyPayment("PurchaseId", "PurchaseType", 2, 3, "CurrencyName");
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names (FString) and amounts of the currencies/resources (int32) .

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FLbLfgSSXw972qP5PCDij%2Fimage.png?alt=media\&token=e3badd3b-ab85-41a4-9231-fbfec87e335e)

| Parameter              | Type                  | Restrictions                                                                 | Restrictions                                                                                                                             |
| ---------------------- | --------------------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***     | FString               | from 1 to 32 symbols                                                         | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***   | FString               | from 1 to 96 symbols                                                         | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`*** | int32                 | from 1 to int32.MaxValue                                                     | The number of units of goods purchased.                                                                                                  |
| ***`resources`***      | TMap\<FString, int32> | <p>FString - from 1 to 24 symbols</p><p>int32 - from 1 to int32.MaxValue</p> | Map with resources.                                                                                                                      |

```cpp
var resources = new Dictionary<string, int>
{
    ["Purchase currency name 1"] = 100,
    ["purchase Currency name 2"] = 10
};
DTDAnalytics.VirtualCurrencyPayment(
    purchaseId: "Purchase ID",
    purchaseType: "Purchase Type",
    purchaseAmount: 100,
    resources: resources);
```

{% endtab %}

{% tab title="Godot" %}

```gdscript
DTDAnalytics.VirtualCurrencyPayment("purchaseID", "purchaseType", 1000, 15, "resource_1")
```

In case the item is sold for more than one currency/resource, you need to build a dictionary with all the names and amounts of the currencies/resources.

```gdscript
var resources = GDDTDInt32Resources.new()
resources.AddValue("resource_1", 700)
resources.AddValue("resource_2", 100)
DTDAnalytics.VirtualCurrencyPaymentWithResources("purchaseID", "purchaseType", 500, resources)
```

| Parameter                | Type   | Restrictions         | Description                                                                                                                              |
| ------------------------ | ------ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
| ***`purchaseId`***       | String | from 1 to 32 symbols | A unique purchase name or ID. Make sure that the names are always in the same language otherwise they will be listed as different items. |
| ***`purchaseType`***     | String | from 1 to 96 symbols | The name of a resource group. For example, for “Wood” it can be “Construction materials”.                                                |
| ***`purchaseAmount`***   | int    | from 1 to Int32.max  | The number of units of goods purchased.                                                                                                  |
| ***`purchaseCurrency`*** | String | from 1 to 24 symbols | The name of a currency used for the purchase.                                                                                            |
| ***`purchasePrice`***    | int    | from 1 to Int32.max  | The price of the purchased item in the specified in-game currency.                                                                       |
| {% endtab %}             |        |                      |                                                                                                                                          |
| {% endtabs %}            |        |                      |                                                                                                                                          |

## Progression event

This event is for games only. It is worthwhile to integrate this event into a game type project, as specified in the [application settings](https://docs.devtodev.com/getting-started/adding-an-app-to-the-space). In projects with the “app” type, game events will not be tracked and displayed in the interface, even if they are integrated. You can verify and change the project type in Settings → [General settings](https://docs.devtodev.com/reports-and-functionality/project-related-reports-and-fuctionality/settings#general-settings).

First of all, the progression event is used in games with short (within one game session) areas/game levels, e.g. match 3 games. You can use the event to collect data on how well or how fast users complete levels, how difficult it is for them, how many resources they gained or spent, and other parameters.

{% tabs fullWidth="true" %}
{% tab title="iOS+macOS (Swift)" %}
There are two methods in working with progression event:

* **`startProgressionEvent`**
* **`finishProgressionEvent`**

When a player spawns at a location, the following method is called:

```swift
let parameters = DTDStartProgressionEventParameters()
parameters.source = "Source"
parameters.setDifficulty(difficulty: 10)

DTDAnalytics.startProgressionEvent(eventName: "Progression event name", 
                                   parameters: parameters)
```

| Parameter         | Type   | Restrictions         | Description                                                              |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`DTDStartProgressionEventParameters`**:

| Parameter          | Type   | Restrictions         | Description                                                                                                     |
| ------------------ | ------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | string | from 1 to 40 symbols | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to Int32.max  | An optional difficulty value which is set using the value: **`setDifficulty(difficulty: Int)`**                 |

Once the player completes the location successfully, the following method is called:

```swift
let parameters = DTDFinishProgressionEventParameters()
parameters.successfulCompletion = true
parameters.duration = 100
parameters.spent = ["currency name 1": 1000,
                    "currency name 2": 50]
parameters.earned = ["currency name 2": 100]

DTDAnalytics.finishProgressionEvent(eventName: "Progression event name", 
                                    parameters: parameters)
```

<table><thead><tr><th>Parameter</th><th width="150">Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>eventName</code></strong></em></td><td>string</td><td>from 1 to 40 symbols</td><td>The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.</td></tr></tbody></table>

**`DTDFinishProgressionEventParameters`**:

<table><thead><tr><th>Parameter</th><th width="150">Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>successfulCompletion</code></strong></em></td><td>bool</td><td>true/false</td><td>The completion event result. ‘<em><strong>True</strong></em>’ if successful, ‘<em><strong>false</strong></em>’ if unsuccessful/lost.</td></tr><tr><td><em><strong><code>duration</code></strong></em></td><td>int</td><td>from 0 to Int64.max</td><td>Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between <strong><code>startProgressionEvent</code></strong> and <strong><code>finishProgressionEvent</code></strong> method calls. </td></tr><tr><td><em><strong><code>spent</code></strong></em></td><td>[String: Int]</td><td><p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p></td><td>Resources consumed during an area completion.</td></tr><tr><td><em><strong><code>earned</code></strong></em></td><td>[String: Int]</td><td><p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p></td><td>Resources earned during an area completion.</td></tr></tbody></table>
{% endtab %}

{% tab title="iOS+macOS (Objective-C)" %}
There are two methods in working with progression event:

* **`startProgressionEvent`**
* **`finishProgressionEvent`**

When a player spawns at a location, the following method is called:

```objectivec
DTDStartProgressionEventParameters * parameters = [[DTDStartProgressionEventParameters alloc] init];
parameters.source = @"Source";
[parameters setDifficultyWithDifficulty:10];

[DTDAnalytics startProgressionEvent:@"Progression event name" withParameters:parameters];
```

| Parameter         | Type     | Restrictions         | Description                                                              |
| ----------------- | -------- | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | NSString | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`DTDStartProgressionEventParameters`**:

| Parameter          | Type      | Restrictions         | Description                                                                                                     |
| ------------------ | --------- | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | NSString  | from 1 to 40 symbols | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | NSInteger | from 0 to Int32.max  | An optional difficulty value which is set using the value: **`setDifficulty(difficulty: Int)`**                 |

Once the player completes the location successfully, the following method is called:

```objectivec
DTDFinishProgressionEventParameters *parameters = [[DTDFinishProgressionEventParameters alloc] init];
parameters.successfulCompletion = true;
parameters.duration = 100;
parameters.spent = @{@"currency name 1": @1000,
                     @"currency name 2": @50};
parameters.earned = @{@"currency name 2": @100};

[DTDAnalytics finishProgressionEvent:@"Progression event name" withParameters:parameters];
```

| Parameter         | Type     | Restrictions         | Description                                                                                                                                       |
| ----------------- | -------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`*** | NSString | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |

**`DTDFinishProgressionEventParameters`**:

| Parameter                    | Type                                   | Restrictions                                                        | Description                                                                                                                                                                                       |
| ---------------------------- | -------------------------------------- | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | BOOL                                   | true/false                                                          | The completion event result. ‘***True***’ if successful, ‘***false***’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | NSInteger                              | from 0 to Int64.max                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`startProgressionEvent`** and **`finishProgressionEvent`** method calls. |
| ***`spent`***                | NSDictionary\<NSString \*,NSNumber \*> | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | NSDictionary\<NSString \*,NSNumber \*> | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p> | Resources earned during an area completion.                                                                                                                                                       |
| {% endtab %}                 |                                        |                                                                     |                                                                                                                                                                                                   |

{% tab title="Android (Kotlin)" %}
There are two methods in working with progression event:

* **`startProgressionEvent`**
* **`finishProgressionEvent`**

When a player spawns at a location, the following method is called:

```kotlin
let parameters = DTDStartProgressionEventParameters()
parameters.source = "Source"
parameters.setDifficulty(difficulty = 10)

DTDAnalytics.startProgressionEvent(
    eventName = "Progression event name", 
    parameters = parameters
)
```

| Parameter         | Type   | Restrictions         | Description                                                              |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`DTDStartProgressionEventParameters`**:

| Parameter          | Type   | Restrictions         | Description                                                                                                     |
| ------------------ | ------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | string | from 1 to 40 symbols | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to Int32.max  | An optional difficulty value which is set using the value: **`setDifficulty(difficulty: Int)`**                 |

Once the player completes the location successfully, the following method is called:

```kotlin
let parameters = DTDFinishProgressionEventParameters()
parameters.successfulCompletion = true
parameters.duration = 100
parameters.spent = ["currency name 1": 1000,
                    "currency name 2": 50]
parameters.earned = ["currency name 2": 100]

DTDAnalytics.finishProgressionEvent(
    eventName = "Progression event name",
    parameters = parameters
)
```

| Parameter         | Type   | Restrictions         | Description                                                                                                                                       |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |

**`DTDFinishProgressionEventParameters`**:

| Parameter                    | Type               | Restrictions                                                        | Description                                                                                                                                                                                       |
| ---------------------------- | ------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | bool               | true/false                                                          | The completion event result. ‘***True***’ if successful, ‘***false***’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | int                | from 0 to Int64.max                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`startProgressionEvent`** and **`finishProgressionEvent`** method calls. |
| ***`spent`***                | Map\<String, Long> | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | Map\<String, Long> | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p> | Resources earned during an area completion.                                                                                                                                                       |
| {% endtab %}                 |                    |                                                                     |                                                                                                                                                                                                   |

{% tab title="Android (Java)" %}
There are two methods in working with progression event:

* **`startProgressionEvent`**
* **`finishProgressionEvent`**

When a player spawns at a location, the following method is called:

```java
DTDStartProgressionEventParameters parameters = new DTDStartProgressionEventParameters();
parameters.setSource("Source");
parameters.setDifficulty(10);
DTDAnalytics.INSTANCE.startProgressionEvent("Progression event name", parameters);
```

| Parameter         | Type   | Restrictions         | Description                                                              |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`DTDStartProgressionEventParameters`**:

| Parameter          | Type   | Restrictions         | Description                                                                                                     |
| ------------------ | ------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | string | from 1 to 40 symbols | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to Int32.max  | An optional difficulty value which is set using the value: **`setDifficulty(difficulty: Int)`**                 |

Once the player completes the location successfully, the following method is called:

```java
Map<String, Long> spendMap = new HashMap<>();
spendMap.put("currency name 1", 1000L);
spendMap.put("currency name 2", 100L);

Map<String, Long> earnedMap = new HashMap<>();
spendMap.put("currency name 2", 100L);

DTDFinishProgressionEventParameters param = new DTDFinishProgressionEventParameters();
param.setSuccessfulCompletion(true);
param.setDuration(100);
param.setSpent(spendMap);
param.setEarned(earnedMap);

DTDAnalytics.INSTANCE.finishProgressionEvent("Progression event name", param);
```

| Parameter         | Type   | Restrictions         | Description                                                                                                                                       |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |

**`DTDFinishProgressionEventParameters`**:

| Parameter                    | Type               | Restrictions                                                        | Description                                                                                                                                                                                       |
| ---------------------------- | ------------------ | ------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | bool               | true/false                                                          | The completion event result. ‘***True***’ if successful, ‘***false***’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | int                | from 0 to Int64.max                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`startProgressionEvent`** and **`finishProgressionEvent`** method calls. |
| ***`spent`***                | Map\<String, Long> | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | Map\<String, Long> | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p> | Resources earned during an area completion.                                                                                                                                                       |
| {% endtab %}                 |                    |                                                                     |                                                                                                                                                                                                   |

{% tab title=".NET Native + UWP" %}
There are two methods in working with progression event:

* **`StartProgressionEvent`**
* **`FinishProgressionEvent`**

When a player spawns at a location, the following method is called:

```csharp
var parameters = new DTDStartProgressionEventParameters();
parameters.Source = "Source";
parameters.Difficulty = 10;
DTDAnalytics.StartProgressionEvent(
    eventName: "Progression event name",
    parameters: parameters);
```

| Parameter         | Type   | Restrictions         | Description                                                              |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`DTDStartProgressionEventParameters`**:

| Parameter          | Type   | Restrictions           | Description                                                                                                     |
| ------------------ | ------ | ---------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | string | from 1 to 40 symbols   | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to int.MaxValue | An optional difficulty value.                                                                                   |

Once the player completes the location successfully, the following method is called:

```csharp
var parameters = new DTDFinishProgressionEventParameters();
parameters.SuccessfulCompletion = true;
parameters.Duration = 100;
parameters.Spent = new Dictionary<string, long>
{
    ["currency name 1"] = 1000,
    ["currency name 2"] = 50
};
parameters.Earned = new Dictionary<string, long>
{
    ["currency name 2"] = 100
};
DTDAnalytics.FinishProgressionEvent(
    eventName: "Progression event name",
    parameters: parameters);
```

| Parameter         | Type   | Restrictions         | Description                                                                                                                                       |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |

**`DTDFinishProgressionEventParameters`**:

| Parameter                    | Type           | Restrictions                                                           | Description                                                                                                                                                                                       |
| ---------------------------- | -------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | bool           | true/false                                                             | The completion event result. ‘**`True`**’ if successful, ‘**`false`**’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | int            | from 0 to int.MaxValue                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`StartProgressionEvent`** and **`FinishProgressionEvent`** method calls. |
| ***`spent`***                | \[String: Int] | <p>key - from 1 to 24 symbols</p><p>value - From 1 to int.MaxValue</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | \[String: Int] | <p>key - from 1 to 24 symbols</p><p>value - From 1 to int.MaxValue</p> | Resources earned during an area completion.                                                                                                                                                       |
| {% endtab %}                 |                |                                                                        |                                                                                                                                                                                                   |

{% tab title="Unity" %}
There are two methods in working with progression event:

* **`StartProgressionEvent`**
* **`FinishProgressionEvent`**

When a player spawns at a location, the following method is called:

```csharp
var parameters = new DTDStartProgressionEventParameters();
parameters.Source = "Source";
parameters.Difficulty = 10;
DTDAnalytics.StartProgressionEvent(
    eventName: "Progression event name",
    parameters: parameters);
```

| Parameter         | Type   | Restrictions         | Description                                                              |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`DTDStartProgressionEventParameters`**:

| Parameter          | Type   | Restrictions             | Description                                                                                                     |
| ------------------ | ------ | ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | string | from 1 to 40 symbols     | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to Int32.MaxValue | An optional difficulty value.                                                                                   |

Once the player completes the location successfully, the following method is called:

```csharp
var parameters = new DTDFinishProgressionEventParameters();
parameters.SuccessfulCompletion = true;
parameters.Duration = 100;
parameters.Spent = new Dictionary<string, long>
{
    ["currency name 1"] = 1000,
    ["currency name 2"] = 50
};
parameters.Earned = new Dictionary<string, long>
{
    ["currency name 2"] = 100
};
DTDAnalytics.FinishProgressionEvent(
    eventName: "Progression event name",
    parameters: parameters);
```

| Parameter         | Type   | Restrictions         | Description                                                                                                                                       |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`*** | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |

**`DTDFinishProgressionEventParameters`**:

| Parameter                    | Type           | Restrictions                                                             | Description                                                                                                                                                                                       |
| ---------------------------- | -------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | bool           | true/false                                                               | The completion event result. ‘**`True`**’ if successful, ‘**`false`**’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | int            | from 0 to Int64.MaxValue                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`StartProgressionEvent`** and **`FinishProgressionEvent`** method calls. |
| ***`spent`***                | \[String: Int] | <p>key - from 1 to 24 symbols</p><p>value - From 1 to Int64.MaxValue</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | \[String: Int] | <p>key - from 1 to 24 symbols</p><p>value - From 1 to Int64.MaxValue</p> | Resources earned during an area completion.                                                                                                                                                       |
| {% endtab %}                 |                |                                                                          |                                                                                                                                                                                                   |

{% tab title="Web" %}
There are two methods in working with progression event:

* **`startProgressionEvent`**
* **`finishProgressionEvent`**

When a player spawns at a location, the following method is called:

```javascript
analytics.startProgressionEvent(eventName, parameters)
```

<table><thead><tr><th width="163">Parameter</th><th>Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><strong><code>eventName</code></strong></td><td>string</td><td>from 1 to 40 symbols</td><td>The name of the event. It is usually the number or the name of the area.</td></tr><tr><td><strong><code>parameters</code></strong></td><td>object</td><td>see below</td><td>Location event parameters.</td></tr></tbody></table>

**`startProgressionEvent`** event parameters:

| Parameter          | Type   | Restrictions                        | Description                                                                                                     |
| ------------------ | ------ | ----------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | string | from 1 to 40 symbols                | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to Number.MAX\_SAFE\_INTEGER | An optional difficulty value.                                                                                   |

Example:

```javascript
analytics.startProgressionEvent("Location 11", {
                difficulty: 10,
                source: "Location 10"
})
```

Once the player completes the location (instead of successfully or not), the following method is called:

```javascript
analytics.finishProgressionEvent("Progression event name", {
                successfulCompletion,
                duration,
                spent,
                earned
})
```

| Parameter          | Type   | Restrictions         | Description                                                                                                                                       |
| ------------------ | ------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`***  | string | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |
| ***`parameters`*** | object | see below            | Location event parameters.                                                                                                                        |

**`startProgressionEvent`** event parameters:

| Parameter                    | Type           | Restrictions                                                                        | Description                                                                                                                                                                                       |
| ---------------------------- | -------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | bool           | true/false                                                                          | The completion event result. ‘***True***’ if successful, ‘***false***’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | int            | from 0 to Number.MAX\_SAFE\_INTEGER                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`startProgressionEvent`** and **`finishProgressionEvent`** method calls. |
| ***`spent`***                | \[String: Int] | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Number.MAX\_SAFE\_INTEGER</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | \[String: Int] | <p>key - from 1 to 24 symbols</p><p>value - from 1 to Number.MAX\_SAFE\_INTEGER</p> | Resources earned during an area completion.                                                                                                                                                       |

Example:

```javascript
analytics.finishProgressionEvent("Location 11", {
                true,
                100,
                spent: {
                                "currency name 1": 1000,
                                "currency name 2": 50 
                },
                earned: {
                                "currency name 2": 100
                }
})
```

{% endtab %}

{% tab title="Unreal" %}
There are two methods in working with progression event:

* **`startProgressionEvent`**
* **`finishProgressionEvent`**

When a player spawns at a location, the following method is called (one of them):

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FAK3BIXbVnHLeGFY4AUpL%2Fimage.png?alt=media\&token=22d6af26-b103-4880-bed2-5c00b490df0f)

<table><thead><tr><th width="163">Parameter</th><th>Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>eventName</code></strong></em></td><td>FString</td><td>from 1 to 72 symbols</td><td>Progression event name.</td></tr></tbody></table>

```cpp
UDTDAnalyticsBPLibrary::StartProgressionEvent("EventName");
```

Start progression event with parameters:

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2FKaUgev9VHW6wYb8kcNMo%2Fimage.png?alt=media\&token=0b105eea-cd44-40fe-88a4-a666fa676f8a)

<table><thead><tr><th width="163">Parameter</th><th>Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>eventName</code></strong></em></td><td>FString</td><td>from 1 to 40 symbols</td><td>The name of the event. It is usually the number or the name of the area.</td></tr><tr><td><em><strong><code>params</code></strong></em></td><td>FDTDStartProgressionEventParams</td><td>see below</td><td>Start progression event parameters.</td></tr></tbody></table>

**FDTDStartProgressionEventParams**:

| Parameter          | Type    | Restrictions             | Description                                                                                                     |
| ------------------ | ------- | ------------------------ | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | FString | from 1 to 40 symbols     | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int32   | from 0 to int32.MaxValue | An optional difficulty value.                                                                                   |

Example:

```cpp
StartProgressionEventParams params;
params.Difficulty = 3;
params.Source = "Source";
UDTDAnalyticsBPLibrary::StartProgressionEventWithParams("EventName", params);
```

Once the player completes the location (instead of successfully or not), the following method is called (one of them):

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2Fuig1FuUQfMKagaRPjjdC%2Fimage.png?alt=media\&token=aa277c5f-d4e9-459e-acb0-5ae357e39246)

<table><thead><tr><th width="163">Parameter</th><th>Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>eventName</code></strong></em></td><td>FString</td><td>from 1 to 72 symbols</td><td>Progression event name.</td></tr></tbody></table>

```cpp
UDTDAnalyticsBPLibrary::FinishProgressionEvent("EventName");window.devtodev.finishProgressionEvent("Progression event name", {
```

Finish progression event with parameters:

<br>

![Blueprint](https://2105883905-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-LnGcP_ZeRJ1ipj9O8dF%2Fuploads%2F8jm712BmAqd9z8UxZwoF%2Fimage.png?alt=media\&token=e1d5f0c8-9848-4ab9-a234-0d21d93ce579)

| Parameter          | Type                             | Restrictions         | Description                                                                                                                                       |
| ------------------ | -------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`eventName`***  | FString                          | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening. |
| ***`parameters`*** | FDTDFinishProgressionEventParams | see below            | Finish progression event parameters.                                                                                                              |

**FDTDFinishProgressionEventParameters**:TMap\<FString, int64>

| Parameter                    | Type                  | Restrictions                                                             | Description                                                                                                                                                                                       |
| ---------------------------- | --------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ***`successfulCompletion`*** | bool                  | true/false                                                               | The completion event result. ‘***True***’ if successful, ‘***false***’ if unsuccessful/lost.                                                                                                      |
| ***`duration`***             | int32                 | from 0 to int32.MaxValue                                                 | Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between **`startProgressionEvent`** and **`finishProgressionEvent`** method calls. |
| ***`spent`***                | TMap\<FString, int64> | <p>key - from 1 to 24 symbols</p><p>value - from 0 to int64.MaxValue</p> | Resources consumed during an area completion.                                                                                                                                                     |
| ***`earned`***               | TMap\<FString, int64> | <p>key - from 1 to 24 symbols</p><p>value - from 0 to int64.MaxValue</p> | Resources earned during an area completion.                                                                                                                                                       |

Example:

```cpp
FDTDFinishProgressionEventParams params;
params.Duration = 200;
params.SuccessfulCompletion = true;
params.Earned.Add("CurrencyName1", 1);
params.Spent.Add("CurrencyName2", 2);
UDTDAnalyticsBPLibrary::FinishProgressionEventWithParams("EventName", params);
```

{% endtab %}

{% tab title="Godot" %}
There are two methods in working with progression event:

* **`StartProgressionEvent`**
* **`FinishProgressionEvent`**

When a player spawns at a location, the following method is called:

```gdscript
var params = GDDTDStartProgressionEventParams.new()
params.SetDifficulty(10)
params.SetSource("source_1")

DTDAnalytics.StartProgressionEventWithParams("ProgressionEventWithParams", params)
```

| Parameter         | Type   | Restrictions         | Description                                                              |
| ----------------- | ------ | -------------------- | ------------------------------------------------------------------------ |
| ***`eventName`*** | String | from 1 to 40 symbols | The name of the event. It is usually the number or the name of the area. |

**`GDDTDStartProgressionEventParameters`**:

| Parameter          | Type   | Restrictions         | Description                                                                                                     |
| ------------------ | ------ | -------------------- | --------------------------------------------------------------------------------------------------------------- |
| ***`source`***     | String | from 1 to 40 symbols | The name of the previous event used for connecting events together. E.g. a previous area visited by the player. |
| ***`difficulty`*** | int    | from 0 to Int32.max  | An optional difficulty value which is set using the value: **`SetDifficulty()`**                                |

Once the player completes the location successfully, the following method is called:

```gdscript
var earnedResources = GDDTDInt64Resources.new()
earnedResources.AddValue("resource_1", 200)
earnedResources.AddValue("resource_2", 1500)
	
var spentResources = GDDTDInt64Resources.new()
spentResources.AddValue("resource_1", 100)
spentResources.AddValue("resource_2", 600)
	
var params = GDDTDFinishProgressionEventParams.new()
params.SetSuccessfulCompletion(true)
params.SetEarnedResources(earnedResources)
params.SetSpentResources(spentResources)
params.SetDuration(2000)
DTDAnalytics.FinishProgressionEventWithParams("ProgressionEventWithParams", params)
```

<table><thead><tr><th>Parameter</th><th width="150">Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>eventName</code></strong></em></td><td>string</td><td>from 1 to 40 symbols</td><td>The name of the event. It is usually the number or the name of the area. It’s important to use the name that was specified at the area’s opening.</td></tr></tbody></table>

**`GDDTDFinishProgressionEventParameters`**:

<table><thead><tr><th>Parameter</th><th width="132">Type</th><th>Restrictions</th><th>Description</th></tr></thead><tbody><tr><td><em><strong><code>successfulCompletion</code></strong></em></td><td>bool</td><td>true/false</td><td>The completion event result. ‘<em><strong>True</strong></em>’ if successful, ‘<em><strong>false</strong></em>’ if unsuccessful/lost.</td></tr><tr><td><em><strong><code>duration</code></strong></em></td><td>int</td><td>from 0 to Int64.max</td><td>Time in seconds taken to complete the area. If not specified, it is automatically calculated as the difference between <strong><code>startProgressionEvent</code></strong> and <strong><code>finishProgressionEvent</code></strong> method calls. </td></tr><tr><td><em><strong><code>spent</code></strong></em></td><td>GDDTDInt64Resources</td><td><p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p></td><td>Resources consumed during an area completion.</td></tr><tr><td><em><strong><code>earned</code></strong></em></td><td>GDDTDInt64Resources</td><td><p>key - from 1 to 24 symbols</p><p>value - from 1 to Int64.max</p></td><td>Resources earned during an area completion.</td></tr></tbody></table>
{% endtab %}
{% endtabs %}

{% hint style="info" %}
The user can only be in one area at a time. When moving to another area (including nested ones), the previous location must be completed. Information about events that have not been completed by calling the **`finishProgressionEvent`** method during a game session (the **`finishProgressionEvent`** method call is not integrated, or the user uses the cached app, or the app has crashed) is not included in the statistics.

If you want to use this event to track actions that take more than one game session, you can prepare the required data and call both methods when the action is completed (successfully or unsuccessfully). For example, you can use this event to track the main questline.
{% endhint %}
