# Unity/.Net

**URL:** https://heroiclabs.com/docs/satori/client-libraries/unity/
**Summary:** The official Unity client handles all communication with the Satori server, enabling management of your live game via sending analytics events, updating properties, getting feature flags and experiments, and more.
**Keywords:** unity client guide, unity client, satori unity, authentication, user accounts, audiences, experiments, live events, feature flags, identities, liveops unity
**Categories:** satori, unity, client-libraries

---


# Satori Unity Client Guide

This client library guide will show you how to use the core Satori features in **Unity**.

## Prerequisites

Before proceeding ensure that you have:

* Access to Satori server instance
* Installed the [Satori Unity SDK](#installation)

{{< note "important" >}}
The Satori client is packaged as part of the Nakama Unity SDK, but using Nakama **is not** required.
{{< / note >}}

### Full API documentation

For the full API documentation please visit the [API docs](https://dotnet.docs.heroiclabs.com/html/namespace_satori.html).

### Installation

The client is available from the:

* [Unity Asset Store](https://assetstore.unity.com/packages/tools/network/nakama-81338)
* [Heroic Labs GitHub releases page](https://github.com/heroiclabs/nakama-unity/releases/latest)

The `Nakama.unitypackage` contains all source code and DLL dependencies required in the client code.

After downloading the file:

* Drag or import it into your Unity project
* Set the editor `Api Compatibility Level` setting to .NET Standard 2.1 (from the **Edit** -> **Project Settings** -> **Player** -> **Other Settings** -> **Configuration** menu).
* From the **Assets** menu create a new C# script and a [client object](#satori-client)

{{< note "" "API Compatibility Level" >}}
While our client is developed against .NET Standard 2.1, the .NET Framework option will work if you need or would like to use it instead.
{{< / note >}}

{{< note "important" "Unity 2019.4.1+">}}
Alternatively, you can checkout a specific release or commit by adding the following to the `manifest.json` file in your project's `Packages` folder:

`"com.heroiclabs.nakama-unity": "https://github.com/heroiclabs/nakama-unity.git?path=/Packages/Nakama#<commit | tag>"`
{{< / note >}}

#### Updates

New versions of the Satori Unity Client and the corresponding improvements are documented in the [Release Notes](../../../nakama/getting-started/release-notes/).

### Unity / .NET SDK Differences

In general, this client guide can also be used in conjunction with the [Satori .NET SDK](https://github.com/heroiclabs/nakama-dotnet) with a few minor differences which are outlined below.

#### Logging

Logging to the console in this guide uses the Unity specific `Debug.LogFormat` method. In the .NET SDK use:

```csharp
Console.WriteLine("Hello, {0}!", "Satori");
```

#### Saving/Loading Data

This guide makes use of Unity's built in `PlayerPrefs` to store and load data. When using the .NET SDK you are free to use whatever data storage/retrieval method you choose, such as saving directly to disk.

## Getting started

Learn how to get started using the Satori Client to manage your live game.

### Satori client

The Satori Client connects to a Satori Server and is the entry point to access Satori features. It is recommended to have one client per server per game.

To create a client pass in your server connection details:

```csharp
const string scheme = "http";
const string host = "127.0.0.1";
const int port = 7450;
const string apiKey = "apiKey";

// Ensure you are using Client from the Satori namespace
var client = new Client(scheme, host, port, apiKey);
```

{{< note "important" "API Keys">}}
Create and rotate API keys in the [Satori Settings page](../../concepts/settings/#api-keys).
{{< / note >}}

### Configuring the request timeout length

Each request to Satori from the client must complete in a certain period of time before it is considered to have timed out. You can configure how long this period is (in seconds) by setting the `Timeout` value on the client:

```csharp
client.Timeout = 10;
```

{{< note "important" "Enable Use System TLS on Android" >}}
Older versions of Unity (2021 and below) ship with their own built-in list of trusted certificates rather than using the ones on the device. If the server's certificate is ever updated, Android builds may suddenly fail to connect, even though the device itself trusts the new certificate.

Enable **Use System TLS** in **Project Settings → Player → Other Settings** to make Unity use the device's certificate list instead. This is worth doing regardless of your Unity version.
{{< / note >}}

## Authentication

Authenticate users using the Satori Client via their unique ID.

When authenticating, you can optionally pass in any desired `defaultProperties` and/or `customProperties` to be updated. If none are provided, the properties remain as they are on the server.

```csharp
// If the user's ID is already stored, grab that - alternatively get the System's unique device identifier.
var id = PlayerPrefs.GetString("id", SystemInfo.deviceUniqueIdentifier);

// If the device identifier is invalid then let's generate a unique one.
if (id == SystemInfo.unsupportedIdentifier)
{
    id = System.Guid.NewGuid().ToString();
}

// Save the user's ID to PlayerPrefs so it can be retrieved during a later play session for re-authenticating.
PlayerPrefs.SetString("id", id);

// Set default identity properties to enable platform-based segmentation.
var defaultProperties = new Dictionary<string, string>
{
    { "platform", Application.platform.ToString() },
    { "language", Application.systemLanguage.ToString() },
    { "client_version", Application.version },
    { "version", Application.version }
};

// Authenticate with the Satori server.
try
{
    Session = await client.AuthenticateAsync(id, defaultProperties);
    Debug.Log("Authenticated successfully.");
}
catch(ApiResponseException ex)
{
    Debug.LogFormat("Error authenticating: {0}", ex.Message);
}
```

When authenticated the server responds with an auth token (JWT) which contains useful properties and gets deserialized into a `Session` object.

### Session lifecycle

Sessions expire after five (5) minutes by default. Expiring inactive sessions is good security practice.

Satori provides ways to restore sessions, for example when players re-launch the game, or refresh tokens to keep the session active while the game is being played.

Use the auth and refresh tokens on the session object to restore or refresh sessions.

Store these tokens in Unity's player preferences:

```csharp
PlayerPrefs.SetString("satori.authToken", session.AuthToken);
PlayerPrefs.SetString("satori.refreshToken", session.RefreshToken);
```

Restore a session without having to re-authenticate:

```csharp
var authToken = PlayerPrefs.GetString("satori.authToken", null);
var refreshToken = PlayerPrefs.GetString("satori.refreshToken", null);
session = Session.Restore(authToken, refreshToken);
```

#### Automatic session refresh

The Unity client library includes a feature where sessions close to expiration are automatically refreshed.

This is enabled by default but can be configured when first creating the [Satori client](#satori-client) using the following parameters:

* `AutoRefreshSession` - Boolean value indicating if this feature is enabled, `true` by default
* `DefaultExpiredTimespan` - The time prior to session expiry when auto-refresh will occur, set to 5 minutes be default

#### Manually refreshing a session

Sessions can be manually refreshed.

```csharp
var session = await client.SessionRefreshAsync(session);
```

### Ending sessions

Logout and end the current session:

```csharp
await client.AuthenticateLogoutAsync(session);
```

## Experiments

Satori [Experiments](../../concepts/experiments/) allow you to test different game features and configurations in a live game.

List the current experiments for this user:

```csharp
var experiments = await client.GetAllExperimentsAsync(session);
```

You can also specify an array of experiment names and/or category labels you wish to get:

```csharp
var experiments = await client.GetExperimentsAsync(session, new [] { "ExperimentOne", "ExperimentTwo" }, new [] { "LabelOne", "LabelTwo" });
```

## Feature flags

Satori [feature flags](../../concepts/remote-configuration/) allow you to enable or disable features in a live game.

### Get a single flag

Get a single feature flag for this user:

```csharp
var flag = await client.GetFlagAsync(session, "FlagName");
```

You can also specify a default value for the flag if a value cannot be found:

```csharp
var flag = await client.GetFlagAsync(session, "FlagName", "DefaultValue");
```

Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.

### Get a single default flag

Get a single default flag for this user:

```csharp
var flag = await client.GetFlagDefaultAsync(session, "FlagName");
```

Similar to the `GetFlagAsync` method, you can also provide a default value for default flags:

```csharp
var flag = await client.GetFlagDefaultAsync("FlagName", "DefaultValue");
```

Specifying a default value ensures no exception will be thrown if the network is unavailable, instead a flag with the specified default value will be returned.

### Listing identity flags

List the available feature flags for this user:

```csharp
var flags = await client.GetFlagsAsync(session, null, null);
```

You can also specify an array of feature flag names and/or by category labels you wish to get:

```csharp
var flags = await client.GetFlagsAsync(session, new [] { "FlagOne", "FLagTwo" }, new [] { "Label1", "Label2" });
```

### Listing default flags

List all default feature flags:

```csharp
var flags = await client.GetFlagsDefaultAsync(null, null);
```

You can also specify an array of feature flag names and/or by category labels you wish to get:

```csharp
var flags = await client.GetFlagsDefaultAsync(new [] { "FlagOne", "FLagTwo" }, new [] { "Label1", "Label2" });
```


## Events

Satori [Events](../../concepts/performance-monitoring/understand-events/) allow you to send data for a given user to the server for processing.

{{< note "important" "Metadata Limits">}}
The maximum size of the `metadata` field is `4096` bytes.
{{< / note >}}

### Sending single events

```csharp
await client.EventAsync(session, new Event("gameFinished", DateTime.UtcNow));
```

### Sending multiple events

```csharp
await client.EventsAsync(session, new[] { new Event("adStarted", DateTime.UtcNow), new Event("appLaunched", DateTime.UtcNow) });
```

## Live events

Satori [Live Events](../../concepts/live-events/) allow you to deliver established features to your players on a custom schedule.

### List live events

Get active, past, and upcoming live events for the authenticated player.

You can optionally include a number of past or future runs, or filter by event name.
- **names** — optional list of event names to filter
- **labels** — optional list of category labels to filter
- **pastRunCount** — number of past runs to return
- **futureRunCount** — number of upcoming runs to return

```csharp
// List live events for the current player.
// Returns ACTIVE events by default.
var activeEvents = await client.GetLiveEventsAsync(session);

// Include 1 past run and 2 upcoming runs
var eventsWithRuns = await client.GetLiveEventsAsync(
    session,
    pastRunCount: 1,
    futureRunCount: 2
);

// Filter by specific event names
var filteredEvents = await client.GetLiveEventsAsync(
    session,
    names: new[] { "Season Pass" },
    pastRunCount: 1,
    futureRunCount: 2
);
```

Each returned event includes a `status` field:
- **TERMINATED** — past events
- **ACTIVE** — ongoing events
- **UPCOMING** — scheduled but not yet started

### Join live event explicitly

```csharp
await client.JoinLiveEventAsync(session, liveEventID);
```

## Identities

Satori [Identities](/docs/satori/concepts/segmentation/) identify individual players of your game and can be enriched with custom properties.

### List an identity's properties

```csharp
var properties = await client.ListPropertiesAsync(session);
```

### Update an identity's properties

```csharp
var defaultProperties = new Dictionary<string, string> {
  { "DefaultPropertyKey", "DefaultPropertyValue" },
  { "AnotherDefaultPropertyKey", "AnotherDefaultPropertyValue" }
};

var customProperties = new Dictionary<string, string> {
  { "CustomPropertyKey", "CustomPropertyValue" },
  { "AnotherCustomPropertyKey", "AnotherCustomPropertyValue" }
};

await client.UpdatePropertiesAsync(session, defaultProperties, customProperties);
```

You can immediately reevaluate the user's audience memberships upon updating their properties by passing `recompute` as `true`:

```csharp
var recompute = true;

await client.UpdatePropertiesAsync(session, defaultProperties, customProperties, recompute);
```

### Identifying a session with a new ID

If you want to submit events to Satori before a user has authenticated with the game backend (e.g. Nakama) and has a User ID, you should authenticate with Satori using a temporary ID, such as the device's unique identifier or a randomly generated one.

```csharp
// If the user's device ID is already stored, grab that - alternatively get the System's unique device identifier.
var deviceId = PlayerPrefs.GetString("deviceId", SystemInfo.deviceUniqueIdentifier);

// If the device identifier is invalid then let's generate a unique one.
if (deviceId == SystemInfo.unsupportedIdentifier)
{
    deviceId = System.Guid.NewGuid().ToString();
}

// Save the user's device ID to PlayerPrefs so it can be retrieved during a later play session for re-authenticating.
PlayerPrefs.SetString("deviceId", deviceId);

// Authenticate with Satori
try
{
    session = await client.AuthenticateAsync(deviceId);
    Debug.Log("Authenticated with Satori");
}
catch(ApiResponseException ex)
{
    Debug.LogFormat("Error authenticating: {0}", ex.Message);
}
```

You can then submit events before the user has authenticated with the game backend.

```csharp
await client.EventAsync(session, new Event("gameLaunched", DateTime.UtcNow));
```

The user would then authenticate with the game backend and retrieve their User ID.

```csharp
var nakamaSession = await nakamaClient.AuthenticateEmailAsync("example@heroiclabs.com", "password");
var userId = nakamaSession.UserId;
```

Once a user has successfully authenticated, you should then call `IdentifyAsync` to enrich the current session and return a new session that should be used for submitting future events.

```csharp
var newSession = await client.IdentifyAsync(session, userId, defaultProperties, customProperties);
```

Note that the old session is no longer valid and cannot be used after this.

### Deleting an identity

Delete the calling identity and its associated data:

```csharp
await client.DeleteIdentityAsync(session);
```

## Messages

Satori [integrates](/docs/satori/concepts/messages/#integrations) with various messaging services to send messages to users via [messages schedules](/docs/satori/concepts/messages/#message-schedules). You can list a user's messages, update a message's status, and delete a message.

### List messages

List all messages for the calling identity:

```csharp
var limit = 10;
var forward = true; // List messages oldest to newest

var messages = await client.GetMessageListAsync(session, limit, forward);
```

### Update message status

Update the status of a message:

```csharp
var id = "messageId";
// Both consumeTime and readTime should be Unix Timestamps
var consumeTime = "1758620600";
var readTime = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(); 

await client.UpdateMessageAsync(session, id, consumeTime, readTime);
```

### Delete message

Delete a scheduled message:

```csharp
var id = "messageId";

await client.DeleteMessageAsync(session, id);
```
