# Getting Started

Getting started with LootLocker couldn't be easier. Follow these five simple steps and you'll be up and running in no time!

### 1️⃣ Create a Free Account

To create your free LootLocker account, visit the [sign-up page on the LootLocker website](https://lootlocker.com/sign-up). In this form you can provide your name, company name, email address, and set a password. Confirm that you agree to the Terms of Service and Privacy Policy, and click Create Free Account.

### 2️⃣ Create a New Game

Once you've created and verified your account, you'll next want to create a new game within LootLocker through our [Web Console](https://console.lootlocker.com/). If you've already created a game and want to create a new one, simply click New Game in the My Games panel in your game dashboard.

### 3️⃣ Install & Configure the SDK

We have open source [SDKs](/the-basics/sdks) for Unreal Engine and Unity. Find our quick start guides below to get set up in a matter of minutes.

* [Unity](/the-basics/unity-quick-start)
* [Unreal Engine](/the-basics/unreal-quick-start)
* [Godot](/the-basics/godot-quick-start)
* [GameMaker](https://lootlocker.com/guides/easy-leaderboards-for-gamemaker-studio-2)
* [Construct 3](https://lootlocker.com/guides/setting-up-online-leaderboards-in-a-construct-3-game)
* [GDevelop](https://lootlocker.com/guides/using-lootlocker-with-gdevelop)

If you're only using HTTPs, check out our [API reference documentation](https://lootlocker.com/reference).

### 4️⃣ Authenticate Your First Player

The first API call you will want your game to make is verifying the player in order to register a session with LootLocker. LootLocker supports three different types of authentication methods. You'll want to select the best method depending on your game and target platforms.

{% hint style="info" %}
We suggesting using Guest Login if you're just getting started with your game's development or still haven't decided which platform your game will be released on.
{% endhint %}

* [**Guest Login**](/players/authentication): This authentication method lets you assign each player a unique ID and is common for web or mobile games where getting the player into the game quickly is important.
* [**Platform Login**](/players/authentication): The Platform Login authentication type covers all login methods using First Party Platforms such as Steam, Xbox, and Sign in with Apple. LootLocker uses the player's authenticated Platform ID to register their session.
* [**White Label Login**](/players/authentication): LootLocker's White Label Login system provides your game with a custom username and password set up so each player can create their own account with your game.

### 5️⃣ Implement LootLocker Features

Now that you've created a session by authenticating your first player, it's time to start implementing other LootLocker features. We've created an [overview of all LootLocker features](/the-basics/what-is-lootlocker) to help you decide which features matches your game the best.

Make sure you also check our our [library of samples](/the-basics/samples), which provide out-of-the-box implementations of a few popular LootLocker features.

### 📌 Resources

Here are some relevant links as you continue to explore LootLocker:

#### 🛟 Looking for Support?

[Here](/the-basics/support) is the best ways to get in touch with us.

#### 📖 Other Resources

* [Blog](https://lootlocker.com/blog)
* [Changelog](https://lootlocker.com/changelog)
* [Guides](https://lootlocker.com/guides)

#### ⚙️ Reference Documentation

* [Game API](https://ref.lootlocker.com/game)
* [Server API](https://ref.lootlocker.com/server)
* [Admin API](https://ref.lootlocker.com/admin)


# What is LootLocker?

LootLocker is the next generation backend that unlocks direct-to-player relationships for developers and publishers. Everything you need to make cross-platform, your platform.

### Overview

LootLocker is made up of four core feature groups.

* [**Player Operations**](/players/overview) encompasses all LootLocker features related to player management, including identification, authentication, profile data storage, and profile management via the LootLocker Web Console. These features provide you with the tools to create, store, and manage player information seamlessly.
* [**Platform Commerce**](/commerce/overview) is a comprehensive feature set in LootLocker that powers in-game economies by enabling the purchase of content using real money or virtual currencies. With Platform Commerce, you can create flexible, fully integrated economy systems in your games.
* [**Content Management**](/content/overview) serves as the central hub for managing all your game’s content across platforms. With Content Management, you can organize, edit, and maintain everything from cosmetics, game configurations, and loot boxes to user-generated content (UGC), all in one place.
* [**Game Systems**](/game-systems/overview) are the core features in LootLocker that drive your game’s progression and player engagement. From meta-systems that keep players coming back, to classification and reward mechanics, to feedback channels for player-created content, Game Systems provide the foundational tools to enhance player retention and interaction.

In addition to these pimrary groups, LootLocker also features several [**Shared Systems**](/shared-systems/overview) not tied to a specific feature but integrated across multiple features.

{% hint style="info" %}
For more a deeper dive into LootLocker's systems and features, check out our [Core Concepts](/the-basics/core-concepts) section.
{% endhint %}

* [**Squire**](/squire-discord-bot/overview) is a Discord bot that connects your Discord community directly to your game using LootLocker, turning Discord from a standalone community hub into an extension of your game’s backend and publishing operations. This enables secure game key distribution, synchronized communication, better community support, and deeper player engagement, all grounded in real in-game activity.

### Player Operations

Includes tools for managing player data, authentication, and profiles via the LootLocker Web Console, making it easy to handle player information.

The following Player Operations features are available in LootLocker:

* [Authentication](/players/authentication): Securely authenticate players using multiple methods, enabling access to your game and its backend features.
* [Broadcasts](/players/broadcasts): Schedule and deliver localized messages to players across one or more of your games.
* [Files](/players/files): Store and manage player-specific files, allowing for personalized data storage within each player’s profile.
* [Friends & Followers](/players/friends-and-followers): Lets players build social connections across platforms by adding friends, following other players, and managing those relationships through a unified player identity.
* [Inventory](/players/inventory): Track and manage the assets a player owns.
* [Names](/players/names): Assign and manage unique display names for players.
* [Metadata](/shared-systems/metadata): Customize and store additional player-specific data.
* [Player Manager](/players/managing-players): Access and update player data, including profile information, inventory, and progression, through the Web Console.
* [Presence](/players/presence): Provides real-time visibility into player sessions (CCU), in-game states, and playtime across all platforms.
* [Multi-User Support](/players/multi-user): Manage multiple players sessions at the same time.
* [Unified Player Accounts](/players/unified-player-accounts): Enable a seamless player experience by connecting multiple platform authentication methods, allowing for unified cross-platform functionality.
* [White Label Login](/players/white-label-login): Authenticate players using a username and password account specifically for your game.

### Platform Commerce

Enables in-game economies by supporting purchases with real money or virtual currencies, allowing you to build integrated and flexible economy systems.

The following Platform Commerce features are available in LootLocker:

* [Catalogs](/commerce/catalogs): Create and manage a collection of purchasable items and assets within your in-game store.
* [Currencies](/commerce/currencies): Define and manage in-game currencies that players can earn, purchase, and spend within the game economy.
* [DLC Management](/commerce/dlc-management): Manage downloadable content (DLC) across various platforms.
* [Entitlements](/commerce/entitlements): Track content entitlements for players, providing a record of purchased items.
* [Real Money Purchases](/commerce/real-money-purchases): Integrate direct real-money transactions, allowing players to buy in-game items or currency with real-world currency.
* [Virtual Purchases](/commerce/virtual-purchases): Enable in-game purchases using virtual currencies, providing players with a range of non-monetary transaction options.
* [Wallets](/commerce/wallets): Manage and store in-game currency balances for players or characters.

### Content Management

Centralizes all game content management, letting you organize and maintain assets like cosmetics, configurations, lootboxes, and user-generated content.

The following Content Management features are available in LootLocker:

* [Assets](/content/working-with-assets): Manage and configure in-game items and game configurations.
* [Loot Box (Asset)](/content/working-with-assets/how-to/rarity-assets): Create loot boxes containing randomized items.
* [Drop Table (Asset)](/content/working-with-assets/how-to/create-a-drop-table): Define probability-based item drops.
* [Game Config (Asset)](/content/working-with-assets/how-to/use-an-asset-to-distribute-a-custom-game-config): Tweak gameplay or game data dynamically while the game is live.
* [User Generated Content (UGC)](/content/ugc-overview): Allow players to create, share, and interact with user-generated content, while providing moderation and curation tools through the web console.
* [Twitch Drops](/content/twitch-drops): Reward players for watching your game on Twitch

### Game Systems

Power progression and engagement with tools for meta-systems, rewards, and player feedback, enhancing retention and interaction.

The following Game Systems features are available in LootLocker:

* [Classes & Heroes](/game-systems/classes-and-heroes): Define and manage base and hero classes, each with unique loadouts and asset equip exceptions.
* [Feedback](/game-systems/feedback): Enable players to provide feedback on each other, user-generated content, or the game itself.
* [Leaderboards](/game-systems/leaderboards): Track player or generic rankings, with options for time-based resets and rewards.
* [Progressions](/game-systems/progressions): Set up point-based progression systems with customizable tiers and rewards.
* [Triggers](/game-systems/triggers): Grant rewards dynamically based on player actions.

### Shared Systems

Shared Systems are features that are not tied to a specific feature but are integrated across multiple features.

The following Shared Systems features are available in LootLocker:

* [Metadata](/shared-systems/metadata): Metadata allows you to store additional, custom information in different formats.
* [Notifications](/shared-systems/notifications): Notifications track and communicate important player-related updates to the game.
* [Player Segments](/shared-systems/player-segments): Let you target certain systems to specific groups of players based on their behavior, activity, platform usage, or play history.

### Squire

A Discord connected to LootLocker that enables secure game key distribution, synchronized communication, better community support, and deeper player engagement, all grounded in real in-game activity.

The following Squire feature are available in LootLocker:

* [Claimable Rewards](/squire-discord-bot/claimable-rewards)
* [Discord Broadcasts](/squire-discord-bot/discord-broadcasts)
* [Platform Keys](/squire-discord-bot/platform-keys)


# Core Concepts

LootLocker may introduce some concepts you might not be familiar with. This section will help you understand how everything works together.

{% hint style="info" %}
If you're looking for a brief description of every LootLocker feature and system, be sure to check out our [Glossary](/the-basics/core-concepts/glossary).
{% endhint %}

<table data-card-size="large" data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-target data-type="content-ref"></th><th data-hidden data-card-cover data-type="files"></th></tr></thead><tbody><tr><td><strong>Players</strong></td><td>Your players are one of the most important parts of your game. That's why LootLocker helps you authenticate them in various ways and easily store different types information and data in their profile like their wallet, inventory, progressions and more.</td><td><a href="/pages/RPttVgpmCCqTg2tdV2Z0">/pages/RPttVgpmCCqTg2tdV2Z0</a></td><td><a href="/files/mVPFFLKSQc1j1HL5lFjt">/files/mVPFFLKSQc1j1HL5lFjt</a></td></tr><tr><td><strong>Assets</strong></td><td>An Asset is an item of property that can be connected to a player - something a player can earn, purchase, or possess. At its core, an asset is an empty shell looking to be assigned a context and populated with relevant data or information. Before assigning a context to an asset, contexts must be created with complexities and other relevant information.</td><td><a href="/pages/fGQirkdXYtXjCOoL8cvC">/pages/fGQirkdXYtXjCOoL8cvC</a></td><td><a href="/files/hrNsEBVVzcURmdAiR1Bl">/files/hrNsEBVVzcURmdAiR1Bl</a></td></tr><tr><td><strong>Character Classes &#x26; Heroes</strong></td><td>A character class lets you define what contexts are associated with each class and what each class' default loadout comprises of. For some games you may not have any classes and can completely ignore this system. Other games might want to take advantage of both character classes and heroes.</td><td><a href="/pages/VffjsHzTxKoRvhmK6fgK">/pages/VffjsHzTxKoRvhmK6fgK</a></td><td><a href="/files/ZYPPD7hkISBrFxeAEADu">/files/ZYPPD7hkISBrFxeAEADu</a></td></tr><tr><td><strong>Web Console</strong></td><td>The LootLocker web console is a browser-based interface that provides you with access to all LootLocker features and the tools needed to manage your games. You can access the web console <a href="https://console.lootlocker.com/">here</a>.</td><td><a href="/pages/wIvc20PlgJfnfGcsC7rJ">/pages/wIvc20PlgJfnfGcsC7rJ</a></td><td><a href="/files/KUt1dXqnurloEZQgpzl8">/files/KUt1dXqnurloEZQgpzl8</a></td></tr><tr><td><strong>API Types</strong></td><td>LootLocker has three different types of APIs (Game, Server, Admin) depending on the needs of your game and development pipeline.</td><td><a href="/pages/EKY86tZ3Ode78P7J7ypu">/pages/EKY86tZ3Ode78P7J7ypu</a></td><td><a href="/files/zcGvGvyap5N4TMKNedI6">/files/zcGvGvyap5N4TMKNedI6</a></td></tr></tbody></table>


# Players

Your players are one of the most important parts of your game. That's why LootLocker helps you authenticate them in various ways and then store different types information and data in their profile.

## Authenticating Players

LootLocker supports three different types of authentication methods. You'll want to select the best method depending on your game and target platforms. Click on each authentication type to learn how to implement it in your game.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Guest Login</strong></td><td>This authentication method lets you assign each player a unique ID and is common for web or mobile games where getting the player into the game quickly is important.</td><td><a href="/files/5Jd5Nrv0orRYEk8cV6tH">/files/5Jd5Nrv0orRYEk8cV6tH</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md">https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md</a></td></tr><tr><td><strong>Platform Login</strong></td><td>The Platform Login authentication type covers all login methods using First Party Platforms such as Steam, Xbox, and Sign in with Apple. LootLocker uses the player's authenticated Platform ID to register their session.</td><td><a href="/files/Zm8aA6hcuJzRzNWKIbGo">/files/Zm8aA6hcuJzRzNWKIbGo</a></td><td><a href="/pages/R5Z51wvNRQlzKBo3Pin1#platform-login">/pages/R5Z51wvNRQlzKBo3Pin1#platform-login</a></td></tr><tr><td><strong>White Label Login</strong></td><td>Our White Label Login system provides your game with a custom username and password set up so each player can create their own account with your game.</td><td><a href="/files/oOkHWU7C7O7WGTObHTqM">/files/oOkHWU7C7O7WGTObHTqM</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/white-label-login.md">https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/white-label-login.md</a></td></tr></tbody></table>

## Player Management

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

The LootLocker Player Manager allows you to easily find and manage players in your game. It allows you to:

* Search for players using a public or platform ID
* View player profiles, including
  * Inventory
  * Currencies
  * Platforms
  * Orders
  * Storage
  * Progression
  * Leaderboards
  * Files
* Grant assets
* Refund assets
* Set profile public / private
* Delete profile
* Add progression XP
* Remove / edit player's leaderboard entries
* Add / edit player key/values

Learn more about the player manager in the link below.

{% content-ref url="/pages/-MXf4ZjzHhr9iUiJ8owg" %}
[Player Manager](/players/managing-players)
{% endcontent-ref %}


# Assets

An asset is an item of property that can be connected to a player - something a player can earn, purchase, or possess. Assets can also be used to store data for your game to read.

## Asset Management

The Asset Manager is an interface to search for, create, and edit all of your game’s assets.

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

### View

The Asset Manager can be viewed in two different ways: List and Grid. To swap between views, click the Change View button in the top right corner of the interface.

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

### Fields

Click the cog icon in the upper right corner of the interface to customize the fields displayed in the Asset Manager.

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

The filed preferences window that appears lets you select which default fields are active as well as activate or deactivate any additional fields that have been created automatically when using the [asset filter](#filters-and-storage) feature.

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

### Search

To search for an asset, enter your search term into the search field in the top right corner of the interface and click Search.

### Filters

Filters can be used to only display assets based on specific criteria. To create a new filter, click the `+ Add Filter` button in the top left corner of the interface. There are three types of filters. Filters can be stacked.

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

#### Created Date

The Created Date lets you filters assets that were created before or after a specific date.

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

#### Asset Filter Key

The Asset Filter Key filter lets you only display assets that have matching data or do not have a matching data in their key field.

<figure><img src="/files/1R905gAWQg6ERrj5uAEd" alt=""><figcaption></figcaption></figure>

#### Asset Filter Value

The Asset Filter Value filter lets you only display assets that have matching data or do not have a matching data in their value field.

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

To delete a filter, simply click the delete icon in the filter you wish to delete. You can also clear all filters at once by clicking Clear Filters.

### Batch Edit Assets

It's possible to select multiple assets at once and perform certain actions on them. To select multiple assets at once simply click the checkmark next to the asset's thumbnail.

<figure><img src="/files/9g6ChXRjotTsLmsGA3EL" alt=""><figcaption></figcaption></figure>

Once multiple assets are selected a new dropdown menu will appear in the top right corner of the interface. This menu provides to following options:

#### Edit Asset Filters

This window allows you to create and edit multiple assets filters at the same time.

<figure><img src="/files/8dqwRD8zVGxHbIa9OC2n" alt=""><figcaption></figcaption></figure>

#### Deactivate Assets

Selecting Deactivate Assets will immediately deactivate the selected assets in the stage environment. These assets will not be available in-game if deactivated.

## Assets

An asset is an item of property that can be connected to a player - something a player can earn, purchase, or possess. At its core, an asset is an empty shell looking to be assigned a context and populated with relevant data or information. Before assigning a context to an asset, contexts must be created with complexities and any other relevant information. This can be done in the [Asset Settings](/admin/game-settings#contexts) menu.

### Asset Contexts

An asset context is a way to group and describe assets. All assets must be assigned an asset context when being created. Defining the context of an asset lets your game know what type of asset it is, and lets LootLocker know what sort of information or logic will be stored in the asset and any special relationships it should have, for example with [character classes](/the-basics/core-concepts/character-classes) and [default loadouts](/the-basics/core-concepts/character-classes#default-loadout).

### Asset Instances

An asset listed in the Asset Manager is considered a reference asset which is never possessed by a player and does not contain dynamic data stored in it by the game. When an asset is granted to the player’s inventory, an asset instance is created from the reference asset. An asset instance is an extension of the asset that the player possesses and contains data specific to that instance. An asset instance is, as the name implies, an instance of an asset and as such if any changes are made to the reference asset, the asset instance will also be updated. This instance can be traded to other players or modified based on the relationship it has to your game.

### Asset Complexities

Asset complexities add additional functionality and logic to an asset context. All asset contexts must be assigned an asset complexity. There are several types of asset complexities. Click on one of the following asset complexities to learn how to configure and integrate them into your game.

<table data-view="cards"><thead><tr><th></th><th></th><th data-hidden data-card-cover data-type="files"></th><th data-hidden data-card-target data-type="content-ref"></th></tr></thead><tbody><tr><td><strong>Generic Assets</strong></td><td>The generic asset complexity is assigned by default when creating an asset context, and does not add any additional functionality or logic to the asset context.</td><td><a href="/files/n26OGumXl4f5FmQuj0T0">/files/n26OGumXl4f5FmQuj0T0</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/create-your-first-asset.md">https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/create-your-first-asset.md</a></td></tr><tr><td><strong>Customization</strong></td><td>The customization complexity adds the ability to store asset variations that can be used for cosmetic items that are the same asset with different visual appearances, for example if a hat in your game has different color variations.</td><td><a href="/files/hrNsEBVVzcURmdAiR1Bl">/files/hrNsEBVVzcURmdAiR1Bl</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/variations/README.md">https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/variations/README.md</a></td></tr><tr><td><strong>Customization Package</strong></td><td>The customization package complexity adds the ability to include multiple assets within an asset, for example when creating a bundle of assets.</td><td><a href="/files/8LMlQA01YiM3dXWOtkvJ">/files/8LMlQA01YiM3dXWOtkvJ</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/asset-package.md">https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/asset-package.md</a></td></tr><tr><td><strong>Loot Box</strong></td><td>The loot box package complexity adds the ability to have LootLocker randomly select an asset from predefined groups of assets, for example when creating a loot box.</td><td><a href="/files/Kpx5rniAfCWRv283Bqm6">/files/Kpx5rniAfCWRv283Bqm6</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/loot-boxes/README.md">https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/loot-boxes/README.md</a></td></tr><tr><td><strong>Rental</strong></td><td>The rental complexity adds the ability to create time-based assets that grant specific assets which expire after a certain amount of time, for example when creating a temporary buff.</td><td><a href="/files/k94SIGVy8NXfaEfY70Mz">/files/k94SIGVy8NXfaEfY70Mz</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/rental-assets-1/README.md">https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/rental-assets-1/README.md</a></td></tr><tr><td><strong>Drop Table</strong></td><td>The drop table complexity adds the ability to have LootLocker randomly select an asset from a predefined table of grouped assets that allows the player to select a certain number of rewards from, for example when creating a drop table for a dungeon boss.</td><td><a href="/files/5BOqlX2Y0kxEOfknFGVX">/files/5BOqlX2Y0kxEOfknFGVX</a></td><td><a href="https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/drop-tables/README.md">https://github.com/lootlocker/gitbook-sync/blob/main/content/working-with-assets/drop-tables/README.md</a></td></tr><tr><td></td><td></td><td></td><td></td></tr><tr><td></td><td></td><td></td><td></td></tr></tbody></table>

## Asset Data

Assets can contain data that can be inputted through the Asset Manager. Asset data is broken down into the following areas:

### Basic Information

<figure><img src="/files/2ZJi1edM1BPLTOUAsu2t" alt=""><figcaption></figcaption></figure>

The following information can be added to the Asset in this section:

* **Name:** The name of the Asset, used in the Asset Manager and in your game.
* **Price:** The price of the Asset, if Currencies are set up.
* **Suggested:** The sum of all Assets in the Package Contents if the Asset has been assigned the Package Complexity.
* **Discount Price:** The optional discount price of the Asset that overrides the price if set at a lower value. If a Discount Price is added, both prices (Price and Discount Price) are still returned to the game so that it is still possible to display the original Price.
* **Universal:** If set to true, a field will be displayed where you can set a start and end date for when the Asset will be Universal. A Universal Asset is added to all player’s Inventories (see below for more information).
* **Featured:** If set to true, a field will be displayed where you can set a start and end date for when the Asset will be set as Featured. Marking an asset as Featured indicates to your game that the Asset is Featured and can be flagged accordingly by your game's interface.
* **Purchasable:** If true, makes this Asset appear in your store, and available for purchase.
* **Unique Instance:** If true, the player can only possess one instance of this Asset.

#### Universal Assets

Universal Assets can be used to temporarily provide players with access to an Asset. While the Asset is set to Universal, it is available in all player’s inventories. When the Asset is no longer marked as Universal, it will be removed from the player’s Inventory unless the player has equipped the Asset previously.

{% hint style="info" %}
The **Last Changed** field indicates the last time information stored in this Asset was edited.
{% endhint %}

### Complexity Specific Data

Based on the Complexity assigned to the Asset Context, additional tabs will become available when viewing an Asset’s properties.

| Complexity            | Complexity Specific Data     |
| --------------------- | ---------------------------- |
| Generic Asset         | N/A                          |
| Customization         | Variations                   |
| Soft Currency Package | Package Contents             |
| Customization Package | Package Contents             |
| Loot Box              | Loot Box Contents            |
| Rental                | Rental Content, Rental Tiers |
| Drop Table            | Drop Table                   |

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

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

The Customization Complexity adds a Variations tab to your Asset Data. This allows you to store Asset Variations that can be used for cosmetic items that are the same Asset with different visual appearances, for example if a hat in your game has different color variations.
{% endtab %}

{% tab title="Soft Currency Package" %}
![](/files/wJJ67QokLo8dEf0RPEzR)

The Soft Currency Package Complexity adds the Package Contents tab to the Asset data. This tab allows you to grant Currency with the Asset, for example when selling a pack of Soft Currencies in your game.
{% endtab %}

{% tab title="Customization Package" %}
![](/files/8fSr7cMQrF65ZBMmetm2)

The Customization Package Complexity adds the Package Contents tab to the Asset data. This allows you to include multiple Assets within an Asset, for example when creating a bundle of Assets.
{% endtab %}

{% tab title="Loot Box" %}
![](/files/H2BQfzQTaN0CEjNoUhMu)

The Loot Box Complexity adds the Loot Box Contents tab to the Asset data. This allows you to have LootLocker randomly select an Asset from a predefined drop group of Assets.
{% endtab %}

{% tab title="Rental" %}
![](/files/9UsQsbS8aNMkA69uSVaK)

The Rental Complexity adds two tabs to the Asset data: Rental Content and Rental Tiers. The Rental Content tab allows you to define what content is included in the Rental Asset.

![](/files/OUGGO1U9gCgVSpapRttH)

The Rental Tiers tab allows you to define the various lengths of time the Asset can be rented for.
{% endtab %}

{% tab title="Drop Table" %}
![](/files/KimWj1iUFAKAaCVEntyf)

The Drop Table Complexity adds a Drop Table tab to your Asset Data. This allows you to define the groups, weights, and drop counts for a Drop Table that LootLocker will base its logic on.
{% endtab %}
{% endtabs %}

### Platform Properties

The Platform Properties section of an Asset contains fields related to the active platforms for your game. Certain platforms allow Assets to contain specific platform information.

<figure><img src="/files/V8j9H7DK0D5gqmGGWJzi" alt="" width="375"><figcaption></figcaption></figure>

### Filters & Storage

The Filters & Storage section of an Asset provides you with the ability to connect custom data to your Asset. The following types of data can be added to your Asset:

* **Filters:** Filters gives you the ability to further categorize Assets in the Asset Manager and your game.
* **Key/Value Storage:** Key/Value Pairs can be added to your Asset to be read by your game. Unlike Filters these values are not used as filters in the Asset Manager.
* **Files:** Upload a file (up to 15MB) to this Asset to be downloaded by your game.
* **Data Entities:** Add and name blob data to your Asset to be read by your game.

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


# Character Classes

Character classes are used to define different types of player characters in your game, what assets they can use or equip, and what assets they have in their inventory by default.

The character class menu can be found in the [settings](/admin/game-settings) menu.

<figure><img src="/files/9WkwAxiKehxdTg27yC5H" alt=""><figcaption></figcaption></figure>

## Default Character Class

The default character class is the character class that a player is assigned when they start your game.

{% hint style="warning" %}
A default character class must always be defined even if you do not use the character class system in your game.
{% endhint %}

### Default Loadout

The default loadout of a character class is used to define what assets a player with the specific character class will be assigned and equipped when their character is created. The default loadout will display empty asset contexts loadout slots that have been assigned to this character class in the [Context Configuration](/admin/game-settings#context-configuration) menu.

## Context Configuration

Contexts are categories to put your assets into in order to assign them a complexity and other useful information. Contexts are used to determine what character classes can use the asset, and if an asset can be detached, or needs to be replaced by another one.

![](/files/-MXflzNsBdMTLrkMePsz)

### Context Settings

Before you start creating assets, you must first create a Context for that asset type and provide the following information:

#### **Name**

A simplified version of the Friendly Name, with no spaces or special characters.

#### **Friendly Name**

A display-friendly version of the Name.

#### **Detachable?**

Detachable Contexts can be unequipped from the player’s loadout. Learn more [below](/admin/game-settings#detachable).

#### **Complexity**

Select what type of Complexity this Context should have. Learn more [here](/content/working-with-assets).

#### **Character Classes**

Select which Character Classes can equip this Context.

#### **User Facing?**

Allow this Context to be visible to players through First Party platforms, like Steam Inventories. This is required for Steam Marketplace to work.

#### **Depends On**

Select a Feature Unlock dependency for this Context.

### Detachable

Asset contexts can be marked as detachable allowing them to be unequipped from the player’s loadout. If an asset context is not set as detachable, then this particular loadout slot must always have an asset equipped in it.

### Feature Unlocks

The feature unlocks context is a default context that cannot be edited. This context can be assigned to assets which can then be used in the depends on field in the context configuration menu. The selected asset must be owned by the player in order for them to access this context.


# API Types

LootLocker has three different types of APIs depending on the needs of your game and development pipeline.

Click on an API to view its reference documentation.

### Game API

The [Game API](https://ref.lootlocker.com/game) is integrated into your game client so that your game can talk to the LootLocker backend.

### Server API

The [Server API](https://ref.lootlocker.com/server) is integrated into your hosted game server so that your game server can talk to the LootLocker backend.

### Admin API

The [Admin API](https://ref.lootlocker.com/admin) provides direct access to LootLocker’s features and can also be used by your game editor.

### Auth Client API

The [Auth Client API](https://ref.lootlocker.com/auth-client) is used for advanced authentication flows such as Unified Player Authentication.


# Web Console

The LootLocker web console is a browser-based interface that provides you with access to all LootLocker features and the tools needed to manage your games.

You can access the web console [here](https://console.lootlocker.com/).

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

## Home Screen

Upon logging into the LootLocker web console you will be taken to the home screen. This screen provides a status summary of your game, links to useful information, ways to get in touch with the LootLocker team, and information about the latest updates to the LootLocker platform.

### Switching Between Games

You can easily switch between games by selecting a game in the `My Games` section of the Home Screen.

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

### Switching Between Organizations

To change organization, select your profile picture in the bottom left corner of the interface and select Games and then a game from a different organization.


# Stage & Live Environments

LootLocker makes it safe and easy to make individual and bulk changes to the data stored with LootLocker through the development environments system. The two environments are Staging and Live.

{% hint style="danger" %}
Usage of the Live Environment is discouraged. It is part of a legacy suite of tools. If you need to use the live game environment, please reach out to the [LootLocker team](https://discord.gg/lootlocker), and we can assist you with the process.
{% endhint %}

LootLocker features a development environment that allows you to make changes safely to your game data, and test changes before they go live in your game. The staging environment (Stage) is where all changes are made and tested. The Live environment is the version of data that your live game points to, and where changes cannot be made directly. To make changes to the Live environment, the changes must be first made in Stage, and then merged into Live.

## **Switching Between Environments**

The currently active Environment is displayed in the Environment Switcher at the bottom of the web console side bar. Clicking on the switcher changes to the other environment.

{% hint style="info" %}
It is only possible to edit data when in the Stage Environment.
{% endhint %}

![](/files/ZmWEBahEj32k2dMlX97s)

![](/files/46vgzzWgYmHkKm6RtFur)

## **Editing Data in Stage Environment**

When working in the web console, all changes made to features, assets, or settings must be made in the Stage environment. These changes can be tested through your game client, explained below.

## **Testing Data in Stage Environment**

To test the data in your Stage environment change the API key in your game to a Stage key. More information about making the authentication request can be found in the [API Reference](https://ref.lootlocker.io/game-api/) documentation or in the [Unity Tutorials](/the-basics/unity-quick-start) or [Unreal Tutorials](/the-basics/unreal-quick-start).

## **Reviewing Changes and Merging Data**

When you are ready for changes to be merged into the Live environment, click the Review Changes link in the side bar. After all of the changes made to the Stage environment have been loaded, you will be able to review and select which changes you would like to merge into the Live environment. Select the changes with the tick box and click Push Selected To Live to merge these changes to the Live environment.

![](/files/QHVtsvRII4yRrG4cz7fn)

Alternatively, you can select Revert Selected to undo the changes you have made to the selected items and restore them back to the values that were last merged into the Live environment.

![](/files/iWjhaXROZHyxZ2RY7uzH)


# Glossary

{% hint style="warning" %}
This page will be updated with more details soon
{% endhint %}

## Game Version

To distinguish and order the versions of your game LootLocker requires that you utilize a numeric version of the [Semantic Versioning standard](https://semver.org/). This is used in different API calls when using the REST API, and in the SDKs it is a required setting to use the SDK.

A numeric semver string consists of between 2 and 4 numbers separated by periods: `X.Y(.Z.B)`. Some valid numeric semver strings would be `1.0`, `0.0.2.6`, `23.6.13`. According to semver standard the position of the numbers is significant and can greatly help you in showing what a version "means". The positions are named `MAJOR`.`MINOR`.`PATCH`.`BUILD`. For LootLocker, the Patch and Build numbers are optional.

MAJOR - Increment this number when you make incompatible and/or very significant changes. This could for example be a complete re-balance of classes, a re-engineering of a crucial system, or a change that makes previous save files unreadable.

MINOR - Increment this number when you change something that isn't crucial, or add stuff to previous systems. This could be for example adding an achievement, moving assets within a level, or making internal tech changes only.

PATCH - This is optional for LootLocker. Increment this number when you make a small change that is very unlikely to have a significant impact on your users. This could be for example a bug fix, some localization changes, or chaning the color of UI items.

BUILD - This is optional for LootLocker. Increment this number when you produce a new build for an existing version. If you for some reason need to distribute a rebuild of version 1.2.3 without additional changes then use this number to make a version 1.2.3.1.

## Identity Provider

Identy provider is the term we use to describe the service used to authenticate the player. This could be a 1st party platform like Steam, Google and Apple, but also White Login or even Guest Login.

This term is an umbrella term for all the different ways a player can authenticate with your game.

## LootLocker Player Identifiers

There are a few different ways to identify a player that permeates the LootLocker systems. This is due to our service evolving over time. While we aim to remove usages of legacy identifiers, you are likely to run into methods using a few of these.

### Player ULID

This is the standard identifier for players across LootLocker's systems. ULID stands for Universally Unique Lexicographically Sortable Identifier and is an industry standard identifying method that is practical for many applications.

The player ulid can in different endpoints be referred to as `player_ulid`, `player_id`, `ulid`, or simply `id`. Regardless, you can recognize it by its string representation format which is a long string (usually 26 characters) of alphanumeric characters. Example: `01BX5ZZKBKACTAV9WEVGEMMVRZ`.

You can find it in all [game api session responses](https://ref.lootlocker.com/game/guest-session) (in both api and SDKs), in the [server `Get Player Info by Game Session Token` endpoint](https://ref.lootlocker.com/server/api-9272399) (in both api and SDKs), or get it from another player identifier using the [Game](https://ref.lootlocker.com/game/api-5291537) or [Server](https://ref.lootlocker.com/server/api-5291669) `Lookup Player Names by IDs` endpoint (in both api and SDKs), and in the LootLocker console player view:

<figure><img src="/files/oUI6bZnKmAcqhqsKyAbn" alt=""><figcaption><p>Screenshot of player view in LootLocker console with Ulid highlighted</p></figcaption></figure>

### Player UID

This is a shorter identifier mainly to be used for public player display. The purpose is to have a shorter identifier that players can write down and share. UID simply stands for Unique Identifier.

The player uid can in different endpoints be referred to as `player_uid`, `player_public_uid`, or `uid`. Regardless, you can recognize it by its string representation format which is a shorter string (usually 12 characters) of alphanumeric characters. Example: `AG5SG99GAJ5R`.

You can find it in all [game api session responses](https://ref.lootlocker.com/game/guest-session) (in both api and SDKs), or get it from another player identifier using the [Game](https://ref.lootlocker.com/game/api-5291537) or [Server](https://ref.lootlocker.com/server/api-5291669) `Lookup Player Names by IDs` endpoint (in both api and SDKs), and in the LootLocker console player view:

<figure><img src="/files/RZBtpxH1BeFgtrIT3IEG" alt=""><figcaption><p>Screenshot of player view in LootLocker console with UID highlighted</p></figcaption></figure>

### Player ID (Legacy)

This is a numerical identifier of the player. This identifier, although still used in many endpoints, is deprecated and LootLocker is phasing it out.

The player id can in different endpoints be referred to as `player_id`, `player_legacy_id`, `legacy_id`, or `id`. Regardless, you can recognize it by that it's the only integer identifier of a player. Example: `9698530`.

You can find it in all [game api session responses](https://ref.lootlocker.com/game/guest-session) (in both api and SDKs), in the [server `Get Player Info by Game Session Token` endpoint](https://ref.lootlocker.com/server/api-9272399) (in both api and SDKs), or get it from another player identifier using the [Game](https://ref.lootlocker.com/game/api-5291537) or [Server](https://ref.lootlocker.com/server/api-5291669) `Lookup Player Names by IDs` endpoint (in both api and SDKs), and in the LootLocker console player view:

<figure><img src="/files/jcJMTW8GzEU0VNN7nsQc" alt=""><figcaption><p>Screenshot of player view in LootLocker console with ID highlighted</p></figcaption></figure>

### Player Identifier

This one is strictly related to guest login. The player identifier is the unique id that "authenticates" a guest user. As such, this is supposed to be a secret for the player that uses it. Any user with access to a player identifier can start a session as that guest user.

The player identifier can in different endpoints be referred to as `player_identifier`, `player_guest_login_id`, or `identifier`. Regardless, you can recognize it by its string representation format. Example: `eefecf08-3b6c-4eec-82f6-322d41d1f2fd`.

You can find it in the [game api guest session response](https://ref.lootlocker.com/game/guest-session) (in both api and SDKs), and in the LootLocker console player view:

<figure><img src="/files/jybokhbBAH18Qv4ZRgoU" alt=""><figcaption><p>Screenshot of player view in LootLocker console with guest login identifier highlighted</p></figcaption></figure>

### Player Platform Identifier

This varies a lot, because there are a few. But you can also identify a player by their external id with another platform or authentication. This can be for example their White Label Email, their Steam ID, or their xbox id.

The player platform identifier can in different endpoints be referred to as `player_platform_identifier`, `platform_identifier`, or by their platform such as `steam_id` or `psn_id`.

You can find it in the [Game `Lookup Player Names by IDs` endpoint](https://ref.lootlocker.com/game/api-5291537) (in both api and SDKs), or in the LootLocker console player view on the right side. Here's an example of how it looks for a Steam player (note that there can be many identifers here for [unified player accounts](/players/unified-player-accounts)):

<figure><img src="/files/kTtPGCxRXBBevEjeA9u8" alt=""><figcaption><p>Screenshot of player view in LootLocker console with steam id highlighted</p></figcaption></figure>

### Player Name

This is a lot more decorative than other identifiers. But can be uniquely identifying players if you enable [`Use Unique Player Names` in your game](https://console.lootlocker.com/settings/game/misc).

The player platform identifier can in different endpoints be referred to as `player_name`, or simply as `name`.

This is the only "identifier" that the player can change, using the [Game `Set Player Name` endpoint](https://ref.lootlocker.com/game/api-5291435) (in both api and SDKs). You as an admin can also change this value for a player in the [console player view](https://console.lootlocker.com/players).

You can find it in all [game api session responses](https://ref.lootlocker.com/game/guest-session) (in both api and SDKs), or get it from another player identifier using the [Game](https://ref.lootlocker.com/game/api-5291537) `Lookup Player Names by IDs` endpoint (in both api and SDKs), and in the LootLocker console player view:

<figure><img src="/files/glmpaZFmnQakWBFya7yk" alt=""><figcaption><p>Screenshot of player view in LootLocker console with player name highlighted</p></figcaption></figure>


# Unity Quick Start

The LootLocker Unity SDK provides an easy way to integrate LootLocker into your Unity project.

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

The best way to get the SDK is through the [Open UPM package registry](https://openupm.com/packages/com.lootlocker.lootlockersdk/). But you can also get it from the [LootLocker Github repository](https://github.com/LootLocker/unity-sdk), either from the source or from [the latest release](https://github.com/lootlocker/unity-sdk/releases/latest).

Installing and configuring the LootLocker SDK in your Unity project only takes a few minutes.

{% hint style="warning" %}
We support all Unity versions after (and including) 2019.1. Please contact us if you require support for a version of Unity prior to that release.
{% endhint %}


# Install the SDK

Installing and configuring the LootLocker SDK in your Unity project only takes a few minutes.

Installing the LootLocker SDK in your project is very straightforward and flexible with different ways to set it up depending on your personal preferences.

The Unity SDK can be installed through the Unity Package Manager or by importing the package into your Unity project. We recommend using the Unity Package Manager.

## Install from Unity Package Manager (Recommended)

#### **Install Using Open UPM (Recommended)**

The best way to get the LootLocker SDK is through the Open UPM registry. This way you will be able to update the SDK inside the Unity Package Manager whenever we release new features or fixes.

Open the Unity Editor and navigate to **Edit/Project Settings/Package Manager.**

<figure><img src="/files/4DASAynFMEOksbmA61sq" alt=""><figcaption></figcaption></figure>

Add a new Scoped Registry (or edit the existing OpenUPM entry): Name=`package.openupm.com,` URL=`https://package.openupm.com` and Scope(s)=`com.lootlocker.lootlockersdk` and click Apply.

What you just did is add the ability for Unity Package Manager to read packages from the extensive OpenUPM registry and then specifically the scope `com.lootlocker.lootlockersdk` which is our Unity SDK.

Now, to actually add the SDK to your project, go to **Window/Package Manager**.

1. Press `+` and select `Add package by name`
2. Paste `com.lootlocker.lootlockersdk` into name
3. Click Add

{% hint style="success" %}
The SDK is now installed in your project, and you are ready to [configure the SDK](/the-basics/unity-quick-start/configure-the-sdk) and [make your first API calls](/the-basics/unity-quick-start/make-your-first-api-call).
{% endhint %}

**Install Using Git**

{% hint style="warning" %}
Before you start, make sure you have Git installed on your computer.
{% endhint %}

Open the Unity editor and navigate to the Package Manager by clicking on `Window` and then `Package Manager`.

![](/files/-MXbbakSyOGu1o92SOfm)

Click on the `+` at the top of the Package Manager window and select `Add package from git URL`.

![](/files/-MXbbhH4PxjncfHStWg5)

Enter the URL below into the text box and click `Add`.

```
https://github.com/LootLocker/unity-sdk.git
```

![](/files/-MXbbmdNFdN15PsSzMNL)

{% hint style="success" %}
The SDK is now installed in your project, and you are ready to[ ](#configure-the-sdk)[configure the SDK](/the-basics/unity-quick-start/configure-the-sdk) and [make your first API calls](/the-basics/unity-quick-start/make-your-first-api-call).
{% endhint %}

## **Install using source code**

If you do not have git installed on your computer you can install the SDK by downloading the entire repository and copying it into the packages folder of your project.

To download the entire repo, select `Code` and `Download Zip` from the [LootLocker Unity SDK GitHub](https://github.com/LootLocker/unity-sdk) page. When done, unzip the downloaded source code into your Unity project's Assets folder.

![](/files/-MZDAVC7KwHpO7BnmDYB)

{% hint style="success" %}
The SDK is now installed in your project, and you are ready to[ ](#configure-the-sdk)[configure the SDK](/the-basics/unity-quick-start/configure-the-sdk) and [make your first API calls](/the-basics/unity-quick-start/make-your-first-api-call).
{% endhint %}

## Install from Git Release

For your convenience we package our releases into `.unitypackage` files that you can drag and drop into your Unity project. Go to [our latest release](https://github.com/lootlocker/unity-sdk/releases/latest), scroll down and find the `.unitypackage` file corresponding to your Unity version, download it, and drag it into your Unity project.

If you are importing the package into an already existing project, make sure you untick the project settings so your settings are not overwritten.

Click Import to complete the SDK installation.

{% hint style="success" %}
The SDK is now installed in your project, and you are ready to[ ](#configure-the-sdk)[configure the SDK](/the-basics/unity-quick-start/configure-the-sdk) and [make your first API calls](/the-basics/unity-quick-start/make-your-first-api-call).
{% endhint %}


# Configure the SDK

Now that you have [installed the SDK](/the-basics/unity-quick-start/install-the-sdk) into your Unity project, you need to connect the Unity project to your game in the [LootLocker Management Console](https://console.lootlocker.com/). The following steps walk you through configuring the LootLocker Unity SDK to work with a game that has already been created in the LootLocker Management Console. If you haven’t created a game or account yet, please visit our [account creation](https://lootlocker.com/sign-up) page.

#### Configure via the LootLocker Unity Extension (Unity v2021.3+)

<figure><img src="/files/8OSQnvnFXTOqxiw09OEe" alt=""><figcaption><p>The LootLocker Unity Extension can be found under the Window menu</p></figcaption></figure>

Open up the LootLocker Unity extension (you can find it under the Window menu in Unity's top bar). When that opens up, login with the account that you use to log in to the [LootLocker Management Console](https://console.lootlocker.com/).

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

Select the game that your Unity Project is for.

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

And now finally select the API key that you want to use. Remember to make sure in the top right corner that you are on the right environment.

Note that if your game project is older then you may still have legacy API Keys. They will not be selectable and clearly marked in the API Key selection list.

#### Configure via Project Settings

Login to the LootLocker Management Console by visiting the [Web Console](https://console.lootlocker.com/settings/api-keys).

Go to `Game Settings/API Keys` and copy your API Key.

Return to the Unity Editor, Click on `Edit`, and then `Project Settings`.

![](/files/-MXhGrAtTmSThfiGIm0l)

Click on `LootLocker SDK` in the list to the left and fill in your API Key copied from the LootLocker dashboard. If the key does not begin with the environment you want to use (`dev_` or `prod_`) you will need to generate a new one for the environment you want to integrate towards.

![](/files/1sOhzfL7UqrWvFpJVBeY)

Fill in **all** information based on data from the LootLocker Settings Menu.

* **Domain Key** is found in Game Settings in the [LootLocker Web Console](https://console.lootlocker.com/settings/api-keys).
* **API Key** is found in Game Settings in the [LootLocker Web Console](https://console.lootlocker.com/settings/api-keys). The API Key will be blurred out in the web console, so to copy it you need to press `reveal <environment> key`.
* **Game Version** refers to the current version of the game in the format 1.2.3.4 (the 3 and 4 being optional but recommended).
* **Current Debug Level** allows you to configure the debug level of the SDK. This can be set to `Errors Only`, `Normal Only`, or `Off`.
* **Allow Token Refresh** can be selected so that the SDK automatically attempts to refresh the session token if it expires. Otherwise the session token needs to be renewed manually.

Deprecated properties

* **Platform** is the name of the platform the game will be built for (e.g Steam, PSN, Android, iOS). **This property has been deprecated** and instead of using this you should call the StartSession method for the platform you want to use.
* **OnDevelopmentMode** lets you test your unpublished changes in the LootLocker Management Console by selecting `Development (true)` instead of `Live (false)`. **This property has been deprecated**, instead use the corresponding api key. Stage keys begin with `dev_` and Live keys begin with `prod_`.

{% hint style="success" %}
You have now configured the LootLocker SDK. In the next section you will learn how to [make your first API calls.](/the-basics/unity-quick-start/make-your-first-api-call)
{% endhint %}


# Authenticate Your First Player

The first API call you'll want to make is verifying the player in order to register a session. LootLocker supports three different types of authentication methods depending on your game.

## 🚪 Guest Login

The Guest Login authentication method lets you assign each player a unique ID. This is common for web games or mobile games where you want to get the player into the game as quick as possible. We suggesting using Guest Login if you're just getting started with your game's development or still haven't decided which platform your game will be released on.

{% content-ref url="/pages/mZA4CXMcTU0eNQ8oyqKv" %}
[Guest Login](/players/authentication/how-to/guest-login)
{% endcontent-ref %}

## ☁️ Platform Login

The Platform Login authentication type covers all login methods using First Party Platforms such as Steam, Xbox, Apple, Google, and many more. LootLocker uses the player's authenticated Platform ID to register their session. Go to the [Authentication](/players/authentication) section and select your preferred platform to see a detailed guide on how to implement it for your game.

## 🗝️ White Label Login

Our White Label Login system provides your game with a custom username and password set up so each player can create their own account with your game.

{% content-ref url="/pages/gVO3Qr6cGMCKAD1etNJ6" %}
[Configure White Label Login](/players/white-label-login/how-to/white-label-login)
{% endcontent-ref %}

## Implement LootLocker Features

Once you've created a session by authenticating your first player, it's time to start implementing other LootLocker features. Check out this overview to figure out which features matches your game the best.

{% content-ref url="/pages/eCjRi3hC7pvuKgeIxNwB" %}
[What is LootLocker?](/the-basics/what-is-lootlocker)
{% endcontent-ref %}


# Update the SDK

## Update through Unity Package Manager

#### **Update SDK installed Using Open UPM (Recommended)**

<figure><img src="/files/pwvwYSXxYufBWN9raEeL" alt=""><figcaption><p>Open UPM Package in need of update</p></figcaption></figure>

When installed through Open UPM the update process is exactly as it is with packages installed through the asset store. When you open the Unity Package Manager the list of packages on the left that needs to be updated has a symbol that looks like an up arrow.

When selected, you can either press the button in the top right to update to the latest version. Or you can select the Version History tab and update to another release.

#### **Update SDK installed by Git**

{% hint style="info" %}
The steps below are the same as for installing the SDK since the Unity Package Manager knows when the SDK has been updated.
{% endhint %}

Open the Unity editor and navigate to the Package Manager by clicking on `Window` and then `Package Manager`.

![](/files/-MXbbakSyOGu1o92SOfm)

Click on the `+` at the top of the Package Manager window and select `Add package from git URL`.

![](/files/-MXbbhH4PxjncfHStWg5)

Enter the URL below into the text box and click `Add`. Copy the URL below into the field.

```
https://github.com/LootLocker/unity-sdk.git
```

![](/files/-MXbbmdNFdN15PsSzMNL)

{% hint style="danger" %}
Remember to reconfigure the SDK as the properties can be overwritten or have changed. You can learn how to configure the SDK [here](/the-basics/unity-quick-start/configure-the-sdk).
{% endhint %}

{% hint style="success" %}
The SDK has now been updated to the latest release.
{% endhint %}


# Unreal Quick Start

The LootLocker Unreal SDK provides an easy way to integrate LootLocker into your Unreal Engine project.

<figure><img src="/files/3gro7KKkwmRGwZ995kkC" alt=""><figcaption></figcaption></figure>

The SDK can be downloaded through the Unreal Marketplace or directly from the LootLocker Github repository:

* [Unreal Marketplace](https://www.unrealengine.com/marketplace/en-US/product/cebab7d09d1c4721bd90375068beb750)
* [Github Repository](https://github.com/LootLocker/unreal-sdk/releases)

{% hint style="warning" %}
We currently support Unreal Engine minimum version of 4.27. Please contact us if you require support for a lower version of Unreal Engine.
{% endhint %}


# Install & Configure the SDK

Installing and configuring the LootLocker SDK in your Unreal project only takes a few minutes.

## Install the SDK

Installing the LootLocker SDK in your project is very straightforward and flexible with different ways to set it up depending on engine version and personal preferences. Once you have downloaded the SDK, follow these steps to install the LootLocker SDK into your Unreal Engine project.

### Install from Unreal Marketplace

In the Epic Games Launcher, search for LootLocker SDK, click on the LootLockerSDK and click `Install to Engine`.

If you were previously using the plugin as a project plugin, delete the LootLockerSDK folder under `Plugins` directory in your existing Unreal project.

Open your Unreal project in the Unreal Editor, navigate to Edit/Plugins in the menu and search for LootLockerSDK.

Tick `Enabled`, and the editor should prompt you to restart Unreal.

Once restarted, click on **File**->**Refresh Visual Studio Project** in the menu.

{% tabs %}
{% tab title="Unreal 5" %}
{% hint style="info" %}
If you don't see "**Refresh Visual Studio Project**" in the File menu, click **Tools**->**New C++ Class**, select **None,** click **Next,** click **Create Class**, when compilation has finished you should see **Refresh Visual Studio Project** in the **File** menu.
{% endhint %}
{% endtab %}

{% tab title="Unreal 4.27 and lower" %}
{% hint style="info" %}
If you don't see "**Refresh Visual Studio Project**" in the File menu, click **File**->**New C++ Class**, select **None,** click **Next,** click **Create Class**, when compilation has finished you should see **Refresh Visual Studio Project** in the **File** menu.
{% endhint %}
{% endtab %}
{% endtabs %}

Click on **File**->**Open Visual Studio**.

Once the solution opens in Visual Studio, navigate to your projects Build.cs file located in;\
**Games** ->”**ProjectName**” -> **Source** -> “**ProjectName**” -> **Source** -> **ProjectName.Build.cs**

Add `{“LootLockerSDK”}` to the `PublicDependencyModuleNames` list (see example below).

```cpp
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "LootLockerSDK" });
```

Add `{“Http”, “Json”, “JsonUtilities”}` to the `PrivateDependencyModuleNames` list (see example below).

```cpp
PrivateDependencyModuleNames.AddRange(new string[] { "HTTP", "Json", "JsonUtilities" });
```

Continue to the next step of the tutorial to [configure the SDK in your project](#configure-the-sdk).

### Install from Github Repository

Download or clone the [repository](https://github.com/LootLocker/unreal-sdk) so that you have it on disk.

Copy the LootLockerSDK folder to the Plugins folder at the root of your Unreal Project. If the Plugins folder does not exist yet, create it.

Right click the `Unreal Engine Project Launch` file in the root of the project and click `Generate Visual Studio Project Files`.

Open your project's .sln file.

Open your project's `Build.cs` file and add the following.

Add `{“LootLockerSDK”}` to the `PublicDependencyModuleNames` list (see example below).

```cpp
PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "LootLockerSDK" });
```

Add `{“HTTP”, “Json”, “JsonUtilities”}` to the `PrivateDependencyModuleNames` list (see example below).

```cpp
PrivateDependencyModuleNames.AddRange(new string[] { "HTTP", "Json", "JsonUtilities" });
```

Continue to the next step of the tutorial to [configure the SDK in your project](#configure-the-sdk).

## Configure the SDK

Now that you have installed the SDK into your Unreal project, you will want to connect the Unreal project to your game in the LootLocker Web Console. The following steps walk you through configuring the LootLocker Unreal Engine SDK to work with a game that has already been created in the LootLocker Web Console. If you haven’t created a game or account yet, please visit [console.lootlocker.com](https://console.lootlocker.com) or follow [this guide](/).

### **Configure the SDK in Project Settings**

<figure><img src="/files/9FUg41vvSNRAJ3nM0yL5" alt=""><figcaption></figcaption></figure>

1. Login to the LootLocker Web Console by visiting [console.lootlocker.com](https://console.lootlocker.com) and navigate to the Settings menu and the [API Keys section](https://console.lootlocker.com/settings/api-keys) in order to copy the Domain and API Keys.
2. In Unreal Engine, navigate to Edit/Project Settings and scroll down the left panel until you reach the Plugins section of the Project Settings. Click on LootLockerSDK.
3. Modify the following information:
   * **API Key** is found in Game Settings in the [LootLocker Web Console](https://console.lootlocker.com/settings/api-keys).
   * **Domain Key** is found in Game Settings in the [LootLocker Web Console](https://console.lootlocker.com/settings/api-keys).
   * **Game Version** refers to the current version of your game in the format 1.2.3.4 (the 3 and 4 being optional but recommended).
   * **Allow Token Refresh** can be selected so that the SDK automatically attempts to refresh the session token if it expires. Otherwise the session token needs to be renewed manually.

Deprecated Properties

* **Platform** is the name of the platform the game will be built for (e.g Steam, PSN, Android, iOS). **This property has been deprecated** and instead of using this, you should call the StartSession method for the platform you want to use.
* **On Development Mode** lets you test your unpublished changes in the LootLocker Management Console `Stage` environment (instead of `Live`). Checking this box will make the SDK target the `Stage` environment. If you want to understand this in more detail, check out the page [Stage & Live Environments](/the-basics/core-concepts/stage-and-live-environments). **This property has been deprecated**, instead use the corresponding api key. Stage keys begin with `dev_` and Live keys begin with `prod_`.

{% hint style="warning" %}
If you update the plugin in the future you might need to repeat some of these steps.
{% endhint %}

You should now have access to the SDK in both your C++ and Blueprint projects.


# Older versions

If you have an old version of the SDK, you can configure the SDK through C++ or Blueprint. In later versions of the SDK (V1.0.1 and later) you must configure the SDK in the Project Settings Plugins menu.

## **Configure the SDK in C++ (SDK V1.0.0)**

Login to the LootLocker Web Console by visiting the [Web Console](https://console.lootlocker.com/) and navigate to the Game Settings menu in order to copy the API Key.

In Unreal Engine, open the `LootLockerConfig.CPP` file located in the Plugins folder.

Modify the following information:

* **API Key** is found in Game Settings in the LootLocker Web Console.
* **Game Version** refers to the current version of the game in the format 1.2.3.4 (the 3 and 4 being optional but recommended).
* **Platform** is the name of the platform the game will be built for (e.g Steam, PSN, Android, iOS).
* **On Development Mode** lets you test your unpublished changes in the LootLocker Management Console `Development` environment (instead of `Live`).
* **Allow Token Refresh** can be selected so that the SDK automatically attempts to refresh the session token if it expires. Otherwise the session token needs to be renewed manually.

Click on "Set as Default" to the top right. Click "Yes" to all popups that follow.

You should now have access to the SDK in both your C++ and Blueprint projects.

## **Configure the SDK in Blueprint (SDK V1.0.0)**

{% hint style="warning" %}
It is important that the LootLocker configurations are set up in the first Blueprint class in your project.
{% endhint %}

Login to the LootLocker Web Console by visiting [console.lootlocker.com](https://console.lootlocker.com) and navigate to the Game Settings menu in order to copy the API Key.

In Unreal Engine, right click within the Event Graph of your Blueprint class, locate and select the `LootLocker Settings` category, and select `Set LootLocker Settings` in that category.

Modify the following information:

* **API Key** is found in Game Settings in the LootLocker Web Console.
* **Game Version** refers to the current version of the game in the format 1.2.3.4 (the 3 and 4 being optional but recommended).
* **Platform** is the name of the platform the game will be built for (e.g Steam, PSN, Android, iOS).
* **On Development Mode** lets you test your unpublished changes in the LootLocker Management Console `Development` environment (instead of `Live`).
* **Allow Token Refresh** can be selected so that the SDK automatically attempts to refresh the session token if it expires. Otherwise the session token needs to be renewed manually.

You have now configured the LootLocker SDK through Blueprint.


# Authenticate Your First Player

The first API call you'll want to make is verifying the player in order to register a session. LootLocker supports three different types of authentication methods depending on your game.

## 🚪 Guest Login

The Guest Login authentication method lets you assign each player a unique ID. This is common for web games or mobile games where you want to get the player into the game as quick as possible. We suggesting using Guest Login if you're just getting started with your game's development or still haven't decided which platform your game will be released on.

{% content-ref url="/pages/mZA4CXMcTU0eNQ8oyqKv" %}
[Guest Login](/players/authentication/how-to/guest-login)
{% endcontent-ref %}

## ☁️ Platform Login

The Platform Login authentication type covers all login methods using First Party Platforms such as Steam, Xbox, Apple, Google, and many more. LootLocker uses the player's authenticated Platform ID to register their session. Go to the [Authentication](/players/authentication) section and select your preferred platform to see a detailed guide on how to implement it for your game.

## 🗝️ White Label Login

Our White Label Login system provides your game with a custom username and password set up so each player can create their own account with your game.

{% content-ref url="/pages/gVO3Qr6cGMCKAD1etNJ6" %}
[Configure White Label Login](/players/white-label-login/how-to/white-label-login)
{% endcontent-ref %}

## Implement LootLocker Features

Once you've created a session by authenticating your first player, it's time to start implementing other LootLocker features. Check out this overview to figure out which features matches your game the best.

{% content-ref url="/pages/eCjRi3hC7pvuKgeIxNwB" %}
[What is LootLocker?](/the-basics/what-is-lootlocker)
{% endcontent-ref %}


# Godot Quick Start

The LootLocker Godot SDK provides an easy way to integrate LootLocker into your Godot project.

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

Download the LootLocker SDK for Godot from the [LootLocker Github repository](https://github.com/LootLocker/godot-sdk).

[Installing](/the-basics/godot-quick-start/install-the-sdk) and [configuring](/the-basics/godot-quick-start/configure-the-sdk) the LootLocker SDK in your Godot project only takes a few minutes.

{% hint style="warning" %}
We support all Godot versions after (and including) 4.3. Please contact us if you require support for a version of Godot prior to that release.
{% endhint %}

{% hint style="warning" %}
A subset of LootLocker features are currently supported in this SDK. If there is a specific feature you need in your game that is yet to be implemented, please reach out to us at <hello@lootlocker.com> or through our Discord community at <https://discord.lootlocker.io/>
{% endhint %}


# Install the SDK

Installing and configuring the LootLocker SDK in your Godot project only takes a few minutes.

You can get the Godot SDK plugin through the [Godot Asset Library](https://godotengine.org/asset-library/asset/3713) or directly from our [GitHub](https://github.com/LootLocker/godot-sdk). Regardless of which method you choose, the process is straightforward.

1. Download the latest version of the Plugin.
   * From [the asset library](https://godotengine.org/asset-library/asset/15595) by pressing `download` on the LootLocker SDK asset corresponding to your version of Godot Engine.
   * OR from GitHub by going to the [latest release](https://github.com/lootlocker/godot-sdk/releases/latest) and downloading the zip file that corresponds to your version of Godot Engine (scroll down to the bottom of the release page, and under the assets menu you can find the zips with the Godot Version in the end of the name).
2. Unpack the zip. This will give you a folder named `LootLockerSDK`
3. Move the `LootLockerSDK` folder into your Godot Project's `addons` folder.
4. Open up your Godot Project and go to Project Settings > Plugins and enable the LootLockerSDK plugin.

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

{% hint style="success" %}
The SDK is now installed in your project, and you are ready to [configure the SDK](/the-basics/godot-quick-start/configure-the-sdk) and [make your first API calls](/the-basics/godot-quick-start/make-your-first-api-call).
{% endhint %}


# Configure the SDK

Now that you have [installed the SDK](/the-basics/godot-quick-start/install-the-sdk) into your Godot project, you need to connect the Godot project to your game in the [LootLocker Management Console](https://console.lootlocker.com/). The following steps walk you through configuring the LootLocker Godot SDK to work with a game that has already been created in the LootLocker Management Console. If you haven’t created a game or account yet, please visit our [account creation](https://lootlocker.com/sign-up) page.

#### Configure via LootLockerSettings.cfg

Log on to the [LootLocker management console](https://console.lootlocker.com/login) and find your Game Settings.

* Find your Game Key in the [API section of the settings](https://console.lootlocker.com/settings/api-keys)
* Open or create the file `res://LootLockerSettings.cfg`. The file follows the ini format and must have the following settings:

  * First, the header `[LootLockerSettings]`
  * Then `api_key="<your api key from console.lootlocker.com>"`
  * Then `domain_key="<your domain key from console.lootlocker.com>"`
  * And finally `game_version="<a semver representation of the current game version>"`
  * Once you've done this, you will have a file that looks something like this:

  ```ini
  [LootLockerSettings]
  ; You can get your api key from https://console.lootlocker.com/settings/api-keys
  api_key="prod_1c52468fc6e8620c955e3b6c303ea8cc"
  ; You can get your domain key from https://console.lootlocker.com/settings/api-keys
  domain_key="1g0glch3"
  ; The game version must follow a semver pattern. Read more at https://semver.org/
  game_version="1.2.1.4"
  ```

{% hint style="success" %}
You have now configured the LootLocker SDK. In the next section you will learn how to [make your first API calls.](/the-basics/godot-quick-start/make-your-first-api-call)
{% endhint %}


# Authenticate Your First Player

The first API call you'll want to make is verifying the player in order to register a session. LootLocker supports three different types of authentication methods depending on your game.

## 🚪 Guest Login

The Guest Login authentication method lets you assign each player a unique ID. This is common for web games or mobile games where you want to get the player into the game as quick as possible. We suggest using Guest Login if you're just getting started with your game's development or still haven't decided which platform your game will be released on.

{% content-ref url="/pages/mZA4CXMcTU0eNQ8oyqKv" %}
[Guest Login](/players/authentication/how-to/guest-login)
{% endcontent-ref %}

## ☁️ Platform Login

The Platform Login authentication type covers all login methods using First Party Platforms such as Steam, Xbox, Apple, Google, and many more. LootLocker uses the player's authenticated Platform ID to register their session. Go to the [Authentication](/players/authentication) section and select your preferred platform to see a detailed guide on how to implement it for your game.

## 🗝️ White Label Login

Our White Label Login system provides your game with a custom username and password set up so each player can create their own account with your game.

{% content-ref url="/pages/gVO3Qr6cGMCKAD1etNJ6" %}
[Configure White Label Login](/players/white-label-login/how-to/white-label-login)
{% endcontent-ref %}

## Implement LootLocker Features

Once you've created a session by authenticating your first player, it's time to start implementing other LootLocker features. Check out this overview to figure out which features matches your game the best.

{% content-ref url="/pages/eCjRi3hC7pvuKgeIxNwB" %}
[What is LootLocker?](/the-basics/what-is-lootlocker)
{% endcontent-ref %}

{% hint style="warning" %}
A subset of LootLocker features are currently supported in this SDK. If there is a specific feature you need in your game that is yet to be implemented, please reach out to us at <hello@lootlocker.com> or through our Discord community at <https://discord.lootlocker.io/>
{% endhint %}


# Update the SDK

Godot does unfortunately not provide a standardized way of updating plugins. To update the SDK, go to [the asset library page](https://godotengine.org/asset-library/asset/15595) or [the latest release on GitHub](https://github.com/lootlocker/godot-sdk/releases/latest) repeat the [install steps](/the-basics/godot-quick-start/install-the-sdk) for the latest version.


# SDKs

{% hint style="info" %}
We're constantly updating and improving our SDKs. Check out our [changelogs](https://lootlocker.com/changelog) to keep up to date.
{% endhint %}

### Unity SDK

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

The best way to get the SDK is through the [Open UPM package registry](https://openupm.com/packages/com.lootlocker.lootlockersdk/). But you can also get it from the [LootLocker Github repository](https://github.com/LootLocker/unity-sdk), either from the source or from [the latest release](https://github.com/lootlocker/unity-sdk/releases/latest). For more detailed instructions, go to [our quick start guide](/the-basics/unity-quick-start).

Once you've downloaded the Unity SDK, follow our [Quick Start Guide](/the-basics/unity-quick-start) to quickly integrate LootLocker into your game.

### Unreal SDK

<figure><img src="/files/3gro7KKkwmRGwZ995kkC" alt=""><figcaption></figcaption></figure>

The LootLocker Unreal SDK can be downloaded from our Github repo or the Unreal Marketplace.

* Download from [Github](https://github.com/LootLocker/unreal-sdk)
* Download from the [Unreal Marketplace](https://www.unrealengine.com/marketplace/en-US/product/cebab7d09d1c4721bd90375068beb750?)

Once you've downloaded the Unreal SDK, follow our [Quick Start Guide](/the-basics/unreal-quick-start) to quickly integrate LootLocker into your game.

### Godot SDK

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

The LootLocker Godot SDK can be downloaded from our Github repo or the Godot Asset Library.

* Download from [Github](https://github.com/lootlocker/godot-sdk)
* Download from the [Godot Asset Library](https://godotengine.org/asset-library/asset/15595)

Once you've downloaded the Godot SDK, follow our [Quick Start Guide](/the-basics/godot-quick-start) to quickly integrate LootLocker into your game.

### Other Game Engine SDKs

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

We want to make sure LootLocker is accessible on as many game engines as possible. While we don't have full SDK support for every game engine yet, we do provide some basic integration support with the following engines:

* [GameMaker](https://lootlocker.com/guides/easy-leaderboards-for-gamemaker-studio-2)
* [Construct 3](https://lootlocker.com/guides/setting-up-online-leaderboards-in-a-construct-3-game)
* [GDevelop](https://lootlocker.com/guides/using-lootlocker-with-gdevelop)


# Samples

We've included samples of many popular features in our SDKs to help you understand and implement the features in your game as quickly and easily as possible.

Links to download the samples and their documentation can be found below.

## Unity SDK Samples

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

The following samples are available for the Unity game engine and can be found in the Unity SDK.

* Download from [Github](https://github.com/LootLocker/unity-sdk)
* Download from the [Unity Asset Store](https://assetstore.unity.com/packages/tools/utilities/lootlocker-sdk-233183)

### Authentication Samples

* [Guest Login (LootLocker Identifier)](/the-basics/samples/authentication-samples#guest-login-lootlocker-identifier)
* [Guest Login (Unique Identifier)](/the-basics/samples/authentication-samples#guest-login-unique-identifier)
* [White Label Login](/the-basics/samples/authentication-samples#white-label-login)

### Leaderboard Samples

* [Generic-type Leaderboard (Arcade Style)](/the-basics/samples/leaderboard-samples#generic-type-leaderboard-arcade-style)
* [Float Leaderboard](/the-basics/samples/leaderboard-samples#float-leaderboard)

### Progressions Samples

* [Simple Progressions](/the-basics/samples/progression-samples#simple-progressions)
* [Multiple Progressions](/the-basics/samples/progression-samples#multiple-progressions)

### Player Samples

* [Player Names](/the-basics/samples/player-samples#player-names)
* [Player Storage](/the-basics/samples/player-samples#player-storage-tab)
* [Player Files](/the-basics/samples/player-samples#player-files)


# Authentication Samples

## Guest Login (LootLocker Identifier)

[Guest Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md) is the most basic type of authentication LootLocker provides as no identity verification is required by the player. This type of authentication is common in mobile games, game jam games, or for guest sessions and smaller games. LootLocker also supports [Platform Login](/players/authentication) and [White Label Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/white-label-login.md) authentication methods.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `1a - GuestLoginLootLockerIdentifier`

This SDK Sample demonstrates Guest Login in action where LootLocker provides the player identifier which is then stored in the PlayerPrefs file. Should the player delete the game, then they will lose access to their game profile.

### In-Engine

In this sample the Player ID displayed is the ID you are assigned by LootLocker when creating a session for the first time.

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

### Web Console

The only web console configuration necessary for this sample is to enable [Guest Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md) located in Game Settings -> Platforms.

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

## Guest Login (Unique Identifier)

[Guest Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md) is the most basic type of authentication LootLocker provides as no identity verification is required by the player. This type of authentication is common in mobile games, game jam games, or for guest sessions and smaller games. LootLocker also supports [Platform Login](/players/authentication) and [White Label Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/white-label-login.md) authentication methods.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `1b - GuestLoginUniqueIdentifier`

This sample is similar to the previous Guest Login SDK Sample, however the unique identifier used in this sample is linked to the player's device instead of an identifier provided by LootLocker.

### In-Engine

In this sample the Player ID displayed is based on your device's unique identifier.

### Web Console

The only web console configuration necessary for this sample is to enable [Guest Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md) located in Game Settings -> Platforms.

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

## White Label Login

LootLocker's [White Label Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/white-label-login.md) system provides you with an out-of-the-box custom game account system where players can create an account for your game with their email address (or username) and password. This system includes email verification and password reset functionality. LootLocker also supports [Guest Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/guest-login.md) and [Platform Login](/players/authentication) authentication methods.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `5 - WhiteLabel`

This sample demonstrates both the new and existing user experience.

### In-Engine

As a `new user` you can create an account and will shortly thereafter receive an account verification email. Once you've created an account you can use the `existing user` fields to login to your account.

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

### Web Console

[White Label Login](https://github.com/lootlocker/gitbook-sync/blob/main/players/authentication/white-label-login.md) is configured by navigating to Game Settings -> Platforms. In this menu you can select the name and reply-to email address used when a player creates an account as well as a few other settings.

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


# Leaderboard Samples

## Player-type Leaderboard

[Leaderboards](/game-systems/leaderboards) are a popular game system in many different games. LootLocker supports two types of leaderboards: generic-type leaderboards and player-type leaderboards.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `2a - PlayerTypeLeaderboard`

This SDK sample demonstrates a player-type leaderboard in action where the leaderboard entry is directly connected to the player meaning that the player can only have one score per leaderboard. This examples also includes metadata where the device system language is used to indicate the country the player is playing in and attaching it to their entry.

### In-Engine

To submit a score, enter a score value and click `Upload Score`. You will then see a score appear in the `Top 10 players` menu.

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

### Web Console

To configure a player-type leaderboard, simply select `Player` when selecting the leaderboard type. You can find a full breakdown of all leaderboard settings in the [feature's documentation](/game-systems/leaderboards).

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

## Generic-type Leaderboard (Arcade Style)

[Leaderboards](/game-systems/leaderboards) are a popular game system in many different games. LootLocker supports two types of leaderboards: generic-type leaderboards and player-type leaderboards.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `2b - GenericTypeLeaderboardArcadeStyle`

This SDK sample demonstrates a generic-type leaderboard in action where the leaderboard entry is not directly connected to the player. In this example this leaderboard type allows players to enter their name whenever they set a score, similar to how it would work in an arcade game. This results in the player being able to submit as many scores as they wish.

### In-Engine

To submit a score, enter a name and score and click `Upload Score`. You will then see a score appear in the `Top 10 players` menu.

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

### Web Console

To configure a generic-type leaderboard, simply select `Generic` when selecting the leaderboard type. You can find a full breakdown of all leaderboard settings in the [feature's documentation](/game-systems/leaderboards).

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

## Float Leaderboard

[Leaderboards](/game-systems/leaderboards) are a popular game system in many different games. LootLocker supports two types of leaderboards: generic-type leaderboards and player-type leaderboards.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `2c - FloatLeaderboard`

This sample demonstrates a float value being used as a score. The float has 4 decimal places. It is then converted to an integer by multiplying and dividing it with / by 1,000 when it's being submitted or retrieved.

### In-Engine

To submit a score, enter a name and score and click `Upload Score`. You will then see a score appear in the `Top 10 players` menu.

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

### Web Console

To configure a generic-type leaderboard, simply select `Generic` when selecting the leaderboard type. You can find a full breakdown of all leaderboard settings in the [feature's documentation](/game-systems/leaderboards).

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


# Progression Samples

## Simple Progressions

[Progressions](/game-systems/progressions) allow you to easily create and manage in-game XP systems, talent trees, battle passes and more through our intuitive web console.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `6 - Simple Progression`

This SDK sample will demonstrate a single progression system in action. The player can progress through all 10 levels (tiers). At level 10 no further points will be added. This example does not have any level-based rewards.

### In-Engine

In this sample you can use the slider to select the number of points to add to the progression. Click `Add points to progression` to add the points to the progression. Once `Current tier` reaches 10 you will be unable to add more points to the progression. Click `Reset Progression` to reset the progression to tier 0.

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

### Web Console

Navigate to Game Systems -> Progressions to create and configure a new progression. Consult the [feature's documentation](/game-systems/progressions) to learn more about progressions.

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

## Multiple Progressions

[Progressions](/game-systems/progressions) allow you to easily create and manage in-game XP systems, talent trees, battle passes and more through our intuitive web console.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `6 - Multiple Progressions`

This SDK sample will build upon the simple progression sample above and demonstrate how multiple progressions can be connected together. In this sample two progressions are linked together where gaining a level (tier) in Progression 1 will also add a point to Progression 2. When Progression 2 reaches level (tier) 2 it will reset Progression 1.

### In-Engine

Like with the previous sample, use the slider to select the number of points to add to each progression and click `Add points to progression`. Each time Progression 1 gains a tier it will add a point to Progression 2. When Progression 2 reaches tier 2 it will reset Progression 1.

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

### Web Console

To configure this particular type of progression systems, you need to use both `Progression Point Rewards` as well as `Progression Reset Rewards`. Learn more about these systems in the [progression feature documentation](/game-systems/progressions).

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


# Player Samples

## Player Names

While it is possible to retrieve [Player Names](/players/names) from 1st part platform IDs (when authenticating with [Platform Login](/the-basics/core-concepts/players#authenticating-players)), you can also optionally let players set their own name or display their public UID. It is also possible to make sure each player's name is unique.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `3 - PlayerNames`

This sample demonstrates allowing the player to set their own player name.

### In-Engine

Enter your name in the field and click `Set player name` to set your player name.

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

### Web Console

Navigate to Settings -> Game Settings to select if your game should accept unique player names.

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

## Player Storage

With LootLocker you can store [key/value pairs](https://github.com/lootlocker/gitbook-sync/blob/main/players/storage/README.md) in a player's profile. These pairs can be created, updated or deleted directly from your game, or manually through the Web Console.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `4 - PlayerStorage`

This SDK Sample demonstrates creating, updating and deleting key/value pairs.

### In-Engine

To create a new key, enter a value in the lower `key` and `value` field and click the `Create/Update key/value` button. The new key/value should be displayed in the table on the right side.

To update a value, enter the correct key and value in the `key` and `value` field and click `Create/Update key/value`.

To delete a key/value, enter the key in the upper `key` field and click `Delete key/value`.

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

### Web Console

Through LootLocker's Web Console it is possible view the key/values that have been stored in the player's profile. You can search for players in the [Player Manager](/players/managing-players) and view their key/values under the `Storage` tab.

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

## Player Files

LootLocker allows you to easily store files of any type in the [player's profile](/players/files). Stored files can be downloaded from a URL. Each file stores up to 5 revisions when updated which can be reverted through the Web Console.

This sample can be found in our [SDK](/the-basics/sdks#unity-sdk) under the name `7 - Player Files`

In this SDK Sample you can upload a file to the player's profile, update it with a new file, and then download the file from LootLocker's servers.

### In-Engine

To upload a new file, click the `Upload/Update File` button. To update a file, select a file and then use the File ID field to indicate which file this new file will be replacing. To download an uploaded file, enter the file ID from the list on the right side and click `Download File`.

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

### Web Console

Through LootLocker's Web Console it is possible view files uploaded to a player's profile, download them, revert the file to previous revisions, or manually upload files from your computer. You can search for players in the [Player Manager](https://github.com/lootlocker/gitbook-sync/blob/main/players/managing-players/player-console-overview.md) and view their uploaded files under the `Files` tab.

<figure><img src="/files/4EEdAdNRYEneHxppVCV8" alt=""><figcaption></figcaption></figure>


# Launching Your Game

When it's time to launch your game make sure you're taken the following steps to have the best experience possible.

## Correct API Key

To ensure players are sent to the right environment, make sure you are using the [correct API Key](/admin/game-settings#api-keys) when building your game.

## Leaderboards

As the [Leaderboard](/game-systems/leaderboards) feature is the only LootLocker feature that doesn't prescribe to the dual environment system, make sure that you are using the correct IDs when submitting scores to the leaderboards.

### Clear Leaderboards

Before launching your game you might also want to [clear your leaderboards](#leaderboards) so that players can set new high scores.

## Building to WebGL for Unity

When building to WebGL, there are some settings which you must change in order for your game to run through WebGL.

### Publisher Settings

Underneath `Project Settings` you will find the `Player` tab, which contains the `Publisher Settings`. Here you must set Compression Format to `Gzip`.

### Other Settings

Underneath `Project Settings` you will find the `Player` tab, which contains the `Other Settings` Find the header called `Optimization` and set Strip Engine Code to `false`.

## Frequently Asked Questions

<details>

<summary>Is the data sent between LootLocker and my game encrypted?</summary>

Yes, LootLocker uses HTTPS encryption for all data in transit between your game and the backend.

</details>

<details>

<summary>Can my users delete their stored data from inside the game?</summary>

Yes, the following APIs can be used to let players delete their stored data in compliance with GDPR and similar laws. Players can also be manually deleted through the LootLocker web console.\
Using these APIs, will set the Player to be deleted in 30 Days.

```birb
curl -X DELETE "https://api.lootlocker.io/game/player" \
  -H "x-session-token: your_token_here"
```

```csharp
LootLockerSDKManager.DeletePlayer((response) => {
     if(response.statusCode == 204)
     {
        //Successfully deleted Player
     }
});
```

The above will return a 204 code as the server has fulfilled the request, and there is no additional data.\\

</details>

## Any Last Questions?

If you're unsure about anything or have any last questions, make sure to join our [Discord](https://discord.gg/lootlocker) where you'll find the entire LootLocker team at your disposal.


# Support

Are you in need of support? Please read this guide so that we can provide you with the best support possible.

We offer support through our [Discord](https://discord.gg/lootlocker).

{% hint style="warning" %}
Our Support Hours are *09:00 - 17:00 CET on weekdays.*
{% endhint %}

### How to Help Us Help You

When asking for support, there are some things you can have in place to get the correct answer faster! Below is a list of things we require in order to help you.

* [Domain Key](https://github.com/lootlocker/gitbook-sync/blob/main/the-basics/support/getting-help.md#domain-key)
* [Trace ID](https://github.com/lootlocker/gitbook-sync/blob/main/the-basics/support/getting-help.md#trace-id)
* [What Did You Expect Should Happen?](https://github.com/lootlocker/gitbook-sync/blob/main/the-basics/support/getting-help.md#a-short-description-of-what-you-expected)
* [What Actually Happened?](https://github.com/lootlocker/gitbook-sync/blob/main/the-basics/support/getting-help.md#a-short-description-of-what-actually-happens)

#### Domain Key

By providing us with your game's Domain Key we're able to quickly learn more about your game and make sure nothing behind the scenes of LootLocker is causing any problems.

{% hint style="danger" %}
Do NOT share this Key with anyone outside of our Support Tickets!
{% endhint %}

To locate your Domain Key, navigate to the Web Console -> Settings -> [API Keys](https://console.lootlocker.com/settings/api-keys)

<figure><img src="/files/bJ8ToW3fQno649rKLJau" alt=""><figcaption><p><a href="https://console.lootlocker.com/settings/api-keys">Domain Key location in the Web Console</a></p></figcaption></figure>

#### Trace ID

The Trace ID provides us with a more descriptive error message. Here's how to get it:

{% tabs %}
{% tab title="Unity" %}
We're forcing an error so we can find the trace\_id.\
We start a session and try to get a progression with an incorrect key. This results in an error.

```csharp
LootLockerSDKManager.GetProgression("IncorrectKey", (response) => 
{
  if(!response.success)
    {
      Debug.Log(response.text);
    }
});
```

You will now be able to find the error in the Console. Select the error message highlighted in blue beneath, to copy the trace\_id easily by then copying it from beneath.

<figure><img src="/files/LpHkI01fezBMacM1gCVE" alt=""><figcaption></figcaption></figure>
{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/fEEaU8Qdcy8PNkctqccc" alt=""><figcaption><p>We don't start a session before this to force an error</p></figcaption></figure>

Here we're attempting to set the profile to public, however, in this case we have not logged in yet to force an error so we can show you where the Trace ID can be found.

To easily copy the Trace ID, ensure you have Output Log open in your Unreal project.

<figure><img src="/files/5fkbThQTi6XMmlSCZNc6" alt=""><figcaption><p>Should you not have this open, navigate to: Window -> Developer Tools -> Output Log.</p></figcaption></figure>

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

From here you can copy the "trace\_id" and send with your message!
{% endtab %}
{% endtabs %}

#### What Did You Expect to Happen?

Please provide a short description of what you expected to happen. This gives us a clear understanding of what you're trying to achieve and helps us understand your goal.

#### What Actually Happened?

Sometimes the devil is in the detail, so please provide a short description outlining what actually happened. This may give us some clues to where to look for the issue.

### Methods of Support

{% tabs %}
{% tab title="Support Channel" %}
Our [Support Channel](https://discord.com/channels/684473225308405795/717723259084341258) should be the first place to solve your issue. Try searching for your question, as it is likely that someone else has had the issue before and a solution is already available! Check out the gif below to learn how to search for specific topics!

<figure><img src="/files/QbBhAaXAySQkP6fMDswZ" alt=""><figcaption><p>Example of searching in support for guest login questions and answers</p></figcaption></figure>

Should you not be able to find your solution, you can send a message specifying what the issue is and what you were expecting to happen. Share some code as well if relevant!

{% hint style="info" %}
Code blocks on Discord allows us to read your code more easily, use them by doing
{% endhint %}

````csharp
```csharp
LootLockerSDKManager.GetProgression("SomeKey", (response) => 
{
  if(!response.success)
    {
      Debug.Log(response.text);
    }
});
```
````

{% endtab %}

{% tab title="Forums" %}
Another way of looking for help is through our [forum channel](https://discord.com/channels/684473225308405795/1019885357342412852). This channel consists of posts that you can filter through tags and search for.

<figure><img src="/files/TVFHCNocUvtBBQmYkbgf" alt=""><figcaption></figcaption></figure>
{% endtab %}
{% endtabs %}


# Error Codes

## Learn more about error codes

Some responses from the LootLocker API will include an error code. These error codes are designed to help you understand what went wrong with your request. Errors containing codes will always follow a common schema:

* `request_id` and `trace_id` are unique identifiers for the request and trace, respectively. These are important pieces of information to provide when contacting support for troubleshooting, as they allow the support team to quickly locate the details of the specific transaction in question.
* `code` indicates the type of error. This is the field you should use to determine how to handle the error in your code. The possible values are documented below.
* `doc_url` provides a link to the documentation for further information about this error code.
* `message` provides a brief, human-readable description of the error. It's important to note that this field should not be used for flow control in your code, as its content may change and is not guaranteed to be consistent. It is intended for debugging and logging purposes, and to provide a readable error message to the end user.

Some error codes may also include additional fields, such as `param` to indicate which parameter was missing or invalid. These fields are not guaranteed to be present outside of the error code they are documented for.

## `internal_server_error`

Internal server error is returned when an unknown, internal error happened. It is recommended to contact LootLocker support with the request id and trace id if this error persists.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "internal_server_error",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#internal_server_error",
  "message": "Internal server error occurred. Please reach out to us with the request id if this issue persists.",
 }
```

***

## `parameter_missing`

Parameter missing is returned when a parameter required for the request is missing.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "parameter_missing",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#parameter_missing",
  "message": "Parameter \"game_id\" is missing",
  "param": "game_id"
}
```

***

## `parameter_invalid`

Parameter invalid is returned when a parameter required for the request is invalid.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "parameter_invalid",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#parameter_invalid",
  "message": "Parameter \"game_id\" is invalid",
  "param": "game_id"
 }
```

***

## `request_body_invalid`

Request body invalid is returned when the request body is invalid. This can be because the request body is empty or it is not valid JSON.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "request_body_invalid",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#request_body_invalid",
  "message": "Request body is not valid JSON"
 }

```

***

## `expectation_not_met`

Expectation not met is returned when the request is valid but does not meet the expectations of the endpoint. This can be because the request contains an invalid parameter, or because the state of the server does not reflect the action being taken.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "expectation_not_met",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#expectation_not_met",
  "message": "Expectation not met: Game is not subscribed",
}

```

***

## `transaction_insufficient_funds`

The `transaction_insufficient_funds` error code is triggered when a player attempts to perform an action that requires more virtual currency than they currently possess.

This error code is specifically returned when a player tries to make a purchase using in-game virtual currency and their balance is insufficient to complete the transaction.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "transaction_insufficient_funds",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#transaction_insufficient_funds",
  "message": "Insufficient funds",
 }
```

## `insufficient_permissions`

The `insufficient_permissions` error code is triggered when a user attempts to perform an action that requires a higher level of permissions than they currently possess.

* Example:

```json
{
  "request_id": "132e392b-5a75-4ecb-99b4-e30610d1a8fe",
  "trace_id": "7c55f99f086390a7731a8430233cb158",
  "code": "insufficient_permissions",
  "doc_url": "https://docs.lootlocker.com/reference/error-codes#insufficient_permissions",
  "message": "Insufficient permissions",
 }
```


# Unreal Marketplace Plugin Support

The Unreal Marketplace according to [§2.6.3d in their guidelines](https://www.unrealengine.com/en-US/marketplace-guidelines#263d) (as per 2023-10-11) supports releasing new software versions of plugins for the latest three engine versions only. This means that if you're using an engine version outside of the latest three releases then we can no longer release updates to your plugin through the marketplace.

### How to continue receiving updates

LootLocker supports engine versions much older than the latest three (4.27 and up as per 2025). So you can continue getting all the fixes and features we release by switching to a github based update flow. Just go to <https://github.com/LootLocker/unreal-sdk> and follow the [installation instructions](https://github.com/LootLocker/unreal-sdk#Installation) (in essence, download the source files and replace your existing LootLocker files).

To keep track of when we release updates, you can subscribe to our changelogs [in discord](https://discord.com/channels/684473225308405795/1108095304609304678) or [subscribe](https://lootlocker.com/changelog) to our [github releases](https://github.com/LootLocker/unreal-sdk/releases).


# Overview

Player Operations encompasses all LootLocker features related to player management, including identification, authentication, profile data storage, and profile management via the LootLocker Web Console. These features provide you with the tools to create, store, and manage player information seamlessly.

The following Player Operations features are available in LootLocker:

* [Authentication](/players/authentication): Securely authenticate players using multiple methods, enabling access to your game and its backend features.
* [Broadcasts](/players/broadcasts): Schedule and deliver localized messages to players across one or more of your games.
* [Files](/players/files): Store and manage player-specific files, allowing for personalized data storage within each player’s profile.
* [Friends & Followers](/players/friends-and-followers): Lets players build social connections across platforms by adding friends, following other players, and managing those relationships through a unified player identity.
* [Inventory](/players/inventory): Track and manage the assets a player owns.
* [Names](/players/names): Assign and manage unique display names for players.
* [Metadata](https://github.com/lootlocker/gitbook-sync/blob/main/players/shared-systems/metadata/README.md): Customize and store additional player-specific data.
* [Game Client Multi User Support](/players/multi-user): How to support and manage multiple local users in your game client.
* [Player Manager](/players/managing-players): Access and update player data, including profile information, inventory, and progression, through the Web Console.
* [Presence](/players/presence): Provides real-time visibility into player sessions (CCU), in-game states, and playtime across all platforms.
* [Unified Player Accounts](/players/unified-player-accounts): Enable a seamless player experience by connecting multiple platform authentication methods, allowing for unified cross-platform functionality.
* [White Label Login](/players/white-label-login): Authenticate players using a username and password account specifically for your game.


# Authentication

LootLocker supports a variety of Authentication methods designed for games of all types. This feature authenticates players using a specific identifier or third-party service, registering a session upon login. Authentication is essential for managing player sessions and unlocking access to all other LootLocker features.

LootLocker offers three types of authentication: Guest Login, Platform Login, and White Label Login. Choosing the right option depends on your game’s requirements and target platforms.

### Guest Login

Guest Login assigns each player a unique ID, providing quick and seamless access. Commonly used in web or mobile games, this method prioritizes fast entry without the need for a traditional login. However, since the unique player ID is not tied to a username and password, it’s not intended for games that require secure identity verification.

### Platform Login

Platform Login supports authentication through First Party Platforms such as Steam, Xbox, and Apple. This method uses the player’s Platform ID to register their session, making it ideal for games integrated with established platform ecosystems.

### White Label Login

White Label Login provides a custom username and password setup, allowing players to create personal accounts directly within your game. This method offers more control over player account management, making it suitable for games that require a standalone authentication system.

{% hint style="info" %}
If you’re considering cross-platform support, explore [Unified Player Accounts](/players/unified-player-accounts) to enable players to link multiple authentication methods, ensuring seamless cross-platform experiences.
{% endhint %}

### Use Cases

* Cross-platform accounts
* Username / Password login
* Simple login for mobile & web games

### How-to (Authenticate With)

* [Guest Login](/players/authentication/how-to/guest-login)
* [Sign in with Apple](/players/authentication/how-to/sign-in-with-apple)
* [Apple Game Center](/players/authentication/how-to/apple-game-center)
* [Sign in with Google](/players/authentication/how-to/sign-in-with-google)
* [Google Play Games](/players/authentication/how-to/google-play-games)
* [Steam](/players/authentication/how-to/steam)
* [PlayStation](/players/authentication/how-to/playstation)
* [Xbox](/players/authentication/how-to/xbox)
* [Nintendo Switch](/players/authentication/how-to/nintendo-switch)
* [Epic Games Store](/players/authentication/how-to/sign-in-with-epic-games)
* [Meta / Oculus](/players/authentication/how-to/meta)
* [White Label Login](/players/white-label-login/how-to/white-label-login)


# How To


# Guest Login

{% hint style="info" %}
If you're not using any specific first party platform or just want something simple, you can use our Guest Login feature to create player sessions.
{% endhint %}

Guest Login provides the player with a simple reusable session which supports most of LootLocker's features, with the exception of platform specific features such as payments through Steam etc.

#### Register a Session

{% tabs %}
{% tab title="Unity" %}
{% hint style="warning" %}
This requires that you have [set up the Unity SDK in your game.](/the-basics/unity-quick-start)
{% endhint %}

We'll start by creating a new empty Game Object in your scene and calling it GameManager. Feel free to skip this if you already have a GameManager or similar in your game.

![](/files/IInMmSCjZSq8F3wTAJq5)

Then in this new GameObject you can add a new script called GameManager

![](/files/owQYNpNSIHeKP2fB39qn)

Open up your new script in your editor of choice and add the following code.

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LootLocker.Requests;

public class GameManager : MonoBehaviour
{
    void Start()
    {
        LootLockerSDKManager.StartGuestSession((response) =>
        {
            if (!response.success)
            {
                Debug.Log("error starting LootLocker session");

                return;
            }

            Debug.Log("successfully started LootLocker session");
        });
    }
}
```

To confirm that everything is running without errors, you can start your game and check the console for the correct Debug.Log message.
{% endtab %}

{% tab title="Unreal" %}
{% hint style="warning" %}
This requires that you have [set up the Unreal SDK in your game.](/the-basics/unreal-quick-start)
{% endhint %}

<figure><img src="/files/awRs8Qc7C629nJRwqBxO" alt="Shows an example of how to implement guest login in Unreal Blueprints - see link: https://blueprintue.com/blueprint/chv8-bf-/"><figcaption><p><a href="https://blueprintue.com/blueprint/chv8-bf-/">Blueprint example of guest login</a></p></figcaption></figure>

Above is an example of how to implement Guest Login using blueprints in Unreal Engine. For an example you can copy and paste into your editor, [look here](https://blueprintue.com/blueprint/chv8-bf-/).

**Input**

You need to exchange the `TriggerGuestLogin` event for whatever event you want to use to trigger the login flow. For Guest Login without a specific identifier you could, for example, trigger it on the `BeginPlay` event.

Remove the node "Optional: Set Player Identifier". The `Guest Login` node can take a Player Identifier input, and if you supply one the player will be logged in with that identifier. Note however that this needs to be unique for each player and there is no authentication tied to the identifier. If you do not input a player identifier a unique one will be generated for you and remembered between game starts.

**Output**

For subsequent calls to different LootLocker methods you will want to create variables from the Player Id and Player Identifier outputs.

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails.
{% endtab %}

{% tab title="Godot" %}
{% hint style="warning" %}
This requires that you have [set up the Godot SDK in your game.](/the-basics/godot-quick-start)
{% endhint %}

Open up the script from which you want to trigger LootLocker Authentication in Godot and add the following code:

```gdscript

var guestLoginResponse = await LL_Authentication.GuestSession.new(playerIdentifier).send()
if(!guestLoginResponse.success) :
  printerr("Guest login failed with reason: " + guestLoginResponse.error_data.to_string())
  return

print("Guest user was successfully signed in to LootLocker")
```

To confirm that everything is running without errors, you can start your game and check the console for the correct message in the log.
{% endtab %}

{% tab title="REST" %}
Register a new player:

<pre class="language-bash"><code class="lang-bash"><strong>curl -X POST "https://api.lootlocker.io/game/v2/session/guest" \
</strong>  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"game_version\": \"0.10.0.0\"}"
</code></pre>

Register an existing player:

```bash
curl -X POST "https://api.lootlocker.io/game/v2/session/guest" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"player_identifier\": \"ec9b35e6-b184-4f34-b49f-980f86b291e2\", \"game_version\": \"0.10.0.0\"}"
```

Example response:

```json
{
  "success": true,
  "session_token": "e6fa44946f077dd9fe67311ab3f188c596df9969",
  "player_id": 3,
  "public_uid": "TSEYDXD8",
  "player_identifier": "ec9b35e6-b184-4f34-b49f-980f86b291e2",
  "player_created_at": "2022-05-30T07:56:01+00:00",
  "check_grant_notifications": true,
  "check_deactivation_notifications": false,
  "seen_before": true
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Guest Login! Next up we suggest you look at our [feature set](/the-basics/what-is-lootlocker#overview), and decide which ones you want to use in your game.
{% endhint %}


# Steam

{% hint style="warning" %}
To use Steam in your game you must be a registered developer and have a game with [SteamWorks](https://partner.steamgames.com/)
{% endhint %}

#### Configure Steam in LootLocker

Go to [Platform Settings](https://console.lootlocker.com/settings/platforms) in the LootLocker Web Console and make sure the Steam platform is enabled.

![LootLocker Steam Settings](/files/MIp3pA7OvLsh528smvNH)

**Steam App ID**

To get the Steam App ID you have to log in to the SteamWorks Partner Dashboard.

![The App ID is behind the red square](/files/rTOrXehz3lyitB3P2fF0)

**Steam Publisher Key**

To find the Steam Publisher Key you can follow [this guide](https://partner.steamgames.com/doc/webapi_overview/auth) from Valve.

#### Install SteamWorks in Your Project

Before we can authenticate with Steam and start a LootLocker session we must have access to a few values from the SteamWorks API.

To learn more about authentication with Steam you can read their documentation for [Session Tickets in SteamWorks](https://partner.steamgames.com/doc/features/auth#client_to_backend_webapi).

{% tabs %}
{% tab title="Unity" %}
Our recommended way to gain access to the SteamWorks API in a Unity Game is by using the 3rd party library called **Steamworks.NET**

Install **Steamworks.NET** using the instructions found here: <http://steamworks.github.io/installation/>
{% endtab %}

{% tab title="Unreal" %}
To learn now to configure your game to work with Steam, please follow the directions found here:

* [Unreal Documentation v4.27 and below](https://docs.unrealengine.com/4.27/en-US/ProgrammingAndScripting/Online/Steam/)
* [Unreal Documentation v5.0 and up](https://docs.unrealengine.com/5.0/en-US/online-subsystem-steam-interface-in-unreal-engine/)
  {% endtab %}

{% tab title="Godot" %}
To learn now to configure your game to work with Steam, please follow the [directions from the official GodotSteam documentation](https://godotsteam.com/).
{% endtab %}
{% endtabs %}

#### Authenticate Player

After adding SteamWorks integration in your project you can go ahead and authenticate the player. If successful, this call will return a lot of data that you can use to display to the Player or make more calls to LootLocker during this session.

{% tabs %}
{% tab title="Unity" %}
We'll start by creating a new empty Game Object in your scene and calling it GameManager. Feel free to skip this if you already have a GameManager or similar in your game.

![Creating empty GameObject](/files/IInMmSCjZSq8F3wTAJq5)

In this new GameObject you can add a new script called GameManager.

![Create new script on GameObject](/files/owQYNpNSIHeKP2fB39qn)

Open up your new script in your editor of choice and add the following code:

```csharp
using LootLocker.Requests;
using Steamworks;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class GameManager : MonoBehaviour
{
    // Consider offsetting this from the start method to avoid startup race conditions
    void Start()
    {
        // To make sure Steamworks.NET is initialized
       if (!SteamManager.Initialized)
        {
            return;
        }
        
        var ticket = new byte[1024];
        var networkIdentity = new SteamNetworkingIdentity();
        HAuthTicket ticketResult = SteamUser.GetAuthSessionTicket(ticket, ticket.Length, out uint actualTicketSize, ref networkIdentity);
        if (ticket.Equals(HAuthTicket.Invalid))
        {
            Debug.LogWarning("Ticket from Steam was invalid: " + ticket.ToString());
            return;
        }

        LootLockerSDKManager.VerifyPlayerAndStartSteamSession(ref ticket, actualTicketSize, (response) =>
        {
            if (!response.success)
            {
                Debug.Log("Error starting a LootLocker session from the Steam User: " + response.errorData.ToString());
                return;
            }
            
            Debug.Log("Successfully started a LootLocker session from Steam User with ID: " + SteamID.ToString());
        });
	}
}
```

{% endtab %}

{% tab title="Unreal" %}
**Retrieve the Steam Session Ticket**

The following steps will help you to retrieve the `SteamSessionTicket` from the Online Subsystem that you set up in [the previous step](#install-steamworks-in-your-project).

Create a new UClass called `USteamSessionHelper` like this (this is the .h file):

```cpp
#pragma once

#include "CoreMinimal.h"

#include "SteamSessionHelper.generated.h"

UCLASS(Blueprintable)
class USteamSessionHelper : public UObject
{
    GENERATED_BODY()
public:
    UFUNCTION(BlueprintCallable, CallInEditor, Category = "<YourProjectName> | SteamSessionHelper")
    static FString GetSteamSessionTicket(int LocalUserNumber);
};
```

Paste the following code in the `.cpp` file:

{% code fullWidth="false" %}

```cpp
// Copyright (c) 2021 LootLocker

#include "SteamSessionHelper.h"

#include "Interfaces/OnlineIdentityInterface.h"
#include "OnlineSubsystem.h"

FString USteamSessionHelper::GetSteamSessionTicket(int LocalUserNumber)
{
    const IOnlineSubsystem* OnlineSubsystem = IOnlineSubsystem::Get(STEAM_SUBSYSTEM);
    if (OnlineSubsystem == nullptr || OnlineSubsystem->GetSubsystemName() != STEAM_SUBSYSTEM)
    {
	//Handle error: "Could not get Steam Online Subsystem"
	return "";
    }

    const IOnlineIdentityPtr IdentityInterface = OnlineSubsystem->GetIdentityInterface();
    if (IdentityInterface == nullptr || !IdentityInterface.IsValid())
    {
	//Handle error: "Could not get Steam Online Subsystem Identity Interface"
	return "";
    }
    
    if(IdentityInterface->GetLoginStatus(LocalUserNumber) != ELoginStatus::LoggedIn)
    {
        //Handle error: "Player is not logged in"
	return "";
    }

    return IdentityInterface->GetAuthToken(LocalUserNumber);
}
```

{% endcode %}

Generate Visual Studio project files and build the project.

You can now use these new methods to get the Steam session ticket from either blueprints or code.

**Verify the Player and Start a LootLocker session**

In Blueprint you can retrieve the token by finding the node you just made above: `GetSteamSessionTicket` in the `<YourProjectName> | SteamSessionHelper` category. This is static so you can find it by right-clicking anywhere within the Event Graph.

Then you can pass the `SteamSessionTicket` to the `Start Steam Session Using Ticket` node which you can find by right-clicking anywhere within the Event Graph.

<figure><img src="/files/OIwXXZynIdrHboFcsAXg" alt="Example implementation in Unreal Blueprints for Steam Player Verification and Session Start using LootLocker - https://blueprintue.com/blueprint/lup-8wln/"><figcaption><p><a href="https://blueprintue.com/blueprint/lup-8wln/">Blueprint example of verifying a steam player and starting a steam session</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/lup-8wln/).

**Input**

You need to exchange the `TriggerSteamAuthentication` event for whatever event you want to use to trigger the login flow.

This example does not include the nodes you made above to get the steam id and session ticket. So make sure to utilize those if you made them. If you're getting the values another way, then that's fine. Simply plug the values into the Verify Player and Start Steam Session node.

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success.

The session response on a successful call also contains a lot of interesting information that you likely want to save, such as `player_id`, `public_uid`, and `player_ulid` among others.

{% hint style="warning" %}
You can not test authentication with Steam in the editor. You need to package your game before it can connect to Steam.
{% endhint %}
{% endtab %}

{% tab title="Godot" %}
Follow the [guide from GodotSteam](https://godotsteam.com/tutorials/) on how to [initialize Steam](https://godotsteam.com/tutorials/initializing/). Once Steam is properly initialized, open up the script from which you want to trigger LootLocker Authentication in Godot and add the following code.

```gdscript

var auth_ticket = Steam.getAuthSessionTicket()
if !auth_ticket.has('buffer'):
    printerr("Steam Auth Session Ticket retrieval failed")
    # Handle error as fit in your code
    
var authTicketString : String = LL_Authentication.ParseSteamAuthTicket(auth_ticket['buffer'], auth_ticket['size'])
if authTicketString.is_empty():
    printerr("Auth ticket could not be parsed")
    # Handle error as fit in your code
    
var steamLoginResponse = await LL_Authentication.SteamSession.new(authTicketString).send()
if(!steamLoginResponse.success) :
    printerr("Login failed with reason: " + steamLoginResponse.error_data.to_string(), true)
    # Handle error as fit in your code
print("Successfully started Steam session with LootLocker")
```

To confirm that everything is running without errors, you can start your game and check the console for the correct message in the log.
{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Steam! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Apple

{% hint style="info" %}
Apple platform needs to be enabled and configured in the Web Console before it can be used in your game. This also requires you to have a developer account with Apple.
{% endhint %}

#### Configuration

To start using Sign in with Apple we first need to configure our integration in the [platform settings](https://console.lootlocker.com/settings/platforms/apple_sign_in)

![](/files/yKC3xapj06D2UVGPhsNG)

**Team ID**

You can find your Apple Team ID by logging in to the Apple developer portal and going to the Membership page. On that page you should be able to see your Team ID.

**Client ID**

Client ID will in most cases be the bundle identifier for your App when you created it in Xcode or in the Apple developer portal.

**Key ID & Certificate**

After logging in to your Apple developer portal, navigate to the "Certificates, Identifiers & Profiles" page. From here you can create a new key.

When creating a key, make sure to enable "Sign in with Apple" and pressing the configure button. The "Primary App ID" should be the same as your game (bundle identifier)

When finished creating your key, make sure you download the certificate, which we will upload in the LootLocker Console.

The Key ID can be seen when clicking the key after creation in the Apple developer portal.

#### Start Session

You need to acquire an [Apple authorization code](https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple) to be able to start an apple session, but once you have that, simply follow along to the code snippet below. In the response from LootLocker you will find a refresh token. We will store this and use it to refresh the session during the lifetime of the refresh token so that you do not need to do the full sign-in flow each time.

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

```csharp
// authCode can be retrieved from appleIDCredential.authorizationCode
string authCode = "put authorizationCode here";
LootLockerSDKManager.StartAppleSession(authCode, (response) =>
{
    if (!response.success)
    {
        Debug.Log("error starting LootLocker session");

        return;
    }

    Debug.Log("session started successfully");
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/Lycq4POwMygmIYBtXP3m" alt="Blueprint example of starting a game session with Apple - https://blueprintue.com/blueprint/f3r3owbk/"><figcaption><p><a href="https://blueprintue.com/blueprint/f3r3owbk/">Blueprint example of starting a game session with Google</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/f3r3owbk/).

**Input**

You need to exchange the `TriggerAppleStartSession` event for whatever event you want to use to trigger the login flow. Remove the node `REPLACE: Apple Auth Code` and replace it with your preferred method of getting the[ Apple Authentication Code](https://developer.apple.com/documentation/authenticationservices/implementing_user_authentication_with_sign_in_with_apple).

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails and what (if any) continued actions you want on success. For subsequent calls to different LootLocker methods, you will want to create variables from the Player Id and Player Identifier outputs. In the session response when starting an Apple session you also get a refresh token that we will save under the hood. But if you want to use it manually, we provide it for you.

Beyond those three, there is a lot of interesting stuff in the session response, like current xp and level for example.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/apple" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"apple_authorization_code\": \"eyJQa....\", \"game_version\": \"1.0.0.0\", \"session_id\": \"8438f4b3-90e4-4cdd-8434-c5c6b4c2c9f1\"}"
```

Example response:

```json
{
  "success": true,
  "session_token": "e6fa44946f077dd9fe67311ab3f188c596df9969",
  "player_id": 3,
  "public_uid": "TSEYDXD8",
  "player_created_at": "2022-05-30T07:56:01+00:00",
  "check_grant_notifications": true,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "748b5ac8-794a-44e3-bb59-c59a2f397e84",
  "player_identifier": "76561198023004363"
}
```

{% endtab %}
{% endtabs %}

#### Refresh Session

With Apple authentication, you can refresh the session instead of having the player do a full sign-in every time. The request will return a 401 (Unauthorized) if the refresh token has expired, in which case you will need to start a new session.

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

```csharp
LootLockerSDKManager.RefreshAppleSession((response) =>
{
    if (!response.success)
    {
        if (response.statusCode == 401) {
            // Refresh token has expired, use StartAppleSession
        }
        else {
            Debug.Log("error starting LootLocker session");
        }

        return;
    }

    Debug.Log("session started successfully");
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/iG2sqztXOMXkpdeWWYJy" alt="Blueprint example of refreshing a game session with Apple - https://blueprintue.com/blueprint/djpexw1a/"><figcaption><p><a href="https://blueprintue.com/blueprint/djpexw1a/">Blueprint example of refreshing a game session with Apple</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/djpexw1a/).

**Input**

You need to exchange the `TriggerAppleRefreshSession` event for whatever event you want to use to trigger the login flow. The node `Refresh Token` is the one you saved [when starting a session](#start-session), but it is optional as we save it behind the scenes as well.

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails and what (if any) continued actions you want on success. For subsequent calls to different LootLocker methods, you will want to create variables from the Player Id and Player Identifier outputs. In the session response when starting an Apple session you also get a refresh token that we will save under the hood. But if you want to use it manually, we provide it for you.

Beyond those three, there is a lot of interesting stuff in the session response, like current xp and level for example.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/apple" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"refresh_token\": \"748b....\", \"player_identifier\": \"765..\", \"game_version\": \"1.0.0.0\", \"session_id\": \"8438f4b3-90e4-4cdd-8434-c5c6b4c2c9f1\"}"
```

Example response:

```json
{
  "success": true,
  "session_token": "e6fa44946f077dd9fe67311ab3f188c596df9969",
  "player_id": 3,
  "public_uid": "TSEYDXD8",
  "player_created_at": "2022-05-30T07:56:01+00:00",
  "check_grant_notifications": true,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "748b5ac8-794a-44e3-bb59-c59a2f397e84",
  "player_identifier": "76561198023004363"
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Apple Game Center! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Apple Game Center

{% hint style="info" %}
Apple Game Center platform needs to be enabled and configured in the Web Console before it can be used in your game. This also requires you to have a developer account with Apple.
{% endhint %}

#### Configuration

{% tabs %}
{% tab title="Unity" %}
First start by installing the [GameKit ](https://developer.apple.com/documentation/gamekit)Unity Plugin found in the [Apple Unity Repository](https://github.com/apple/unityplugins).

Then in the Unity Editor menu, go to **Edit** > **Project Settings…**, then select **Services** > **Authentication** from the navigation menu.

Set **ID Providers** to **Apple Game Center**, then select **Add**.

Enter the **Bundle ID** from the Apple developer console in the **Bundle ID** text field, then select **Save**. The Bundle ID should look like this: "**com.lootlocker.hector**".
{% endtab %}

{% tab title="Unreal" %}
**Please see Unreal's** [**guide**](https://docs.unrealengine.com/4.27/en-US/SharingAndReleasing/Mobile/iOS/Setup/) **on how to configure your project to gain access to the necessary calls to use Apple Game Center Authentication.**
{% endtab %}
{% endtabs %}

{% hint style="danger" %}
The timestamp input will only be valid for 30 minutes for security reasons.
{% endhint %}

#### Start Session

{% hint style="info" %}
We do store the Refresh Token for the next time the player starts your game. This Token can be used to Refresh a Session which uses a simpler flow.
{% endhint %}

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

```csharp
string bundleId;
string signature;
string playerId;
string salt;
string publicKeyUrl;
string timestamp;

LootLockerSDKManager.StartAppleGameCenterSession(bundleId, playerId, publicKeyUrl, signature, salt, timestamp, (response) => 
{

    if (!response.success)
    {
        Debug.Log("error starting LootLocker session");

        return;
     }

     Debug.Log("successfully started LootLocker session");

});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/xZPnelqxKq82DIa4hSE1" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/ftj1rgjz/">Blueprint example of starting an apple game center session</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/apple/game-center" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"bundle_id\": \"apple.bundle.id\", \"player_id\": \"T:_e66eaa73fcd186b1b3e9dc22e19398e4\", \"public_key_url\": \"https://static.gc.apple.com/public-key/gc-prod-9.cer\", \"signature\": \"x+D8j5ulh6/m1O6yW5mKuaX ... G3vEwHLxsYGLjSVn53XVshIFL528=\", \"salt\": \"UIzs/Q==\", \"timestamp\": 1680781286451, \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": false,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

#### Refresh Session

Refreshing a Session can be done in order to start the session without going through the entire StartAppleGameCenterSession() flow, if your session token is still valid.

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

```csharp
LootLockerSDKManager.RefreshAppleGameCenterSession((response) => 
{     
    if (!response.success)
    {
        if (response.statusCode == 401) {
            // Refresh token has expired, use StartAppleGameCenterSession
        }
        else {
            Debug.Log("error starting LootLocker session");
        }

        return;
    }

    Debug.Log("session started successfully");   
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/xbCNAiYc2fZeyMLqh7cJ" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/1bgf0-zk/">Blueprint example of refreshing an apple game center session</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/apple/game-center" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"refresh_token\": \"748b....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": false,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Apple Game Center! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Sign in with Google

{% hint style="info" %}
The Google platform needs to be enabled and configured in the Web Console before it can be used in your game.
{% endhint %}

#### Configuration

To start using Sign in with Google we first need to configure our integration in the [platform settings](https://console.lootlocker.com/settings/platforms/google_sign_in).

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

**Client ID**

Client ID can be retrieved from the [Google Dev Console](https://console.cloud.google.com/apis/credentials). If you don't have an OAuth2 Client ID, you will have to create one.

#### Start Session

You need to acquire a [Google Identity Token](https://cloud.google.com/docs/authentication/get-id-token) to be able to start a Google session, but once you have that, simply follow along to the code snippet below. In the response from LootLocker you will find a refresh token which can be used to refresh the session so that you do not need to do the full sign-in flow each time.

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

```csharp
string idToken = "eyJhbGciOiJSUz............";
LootLockerSDKManager.StartGoogleSession(idToken, (response) =>
{
    if (!response.success)
    {
        Debug.Log("error starting LootLocker session");

        return;
    }

    Debug.Log("session started successfully");

    // Store these to be able to refresh the session without using the full sign in flow
    string refreshToken = response.refresh_token;
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/gqIddV3j1XLHk8sffz0H" alt="Blueprint example of starting a game session with Google - https://blueprintue.com/blueprint/7j49kuw8/"><figcaption><p><a href="https://blueprintue.com/blueprint/7j49kuw8/">Blueprint example of starting a game session with Google</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/7j49kuw8/).

**Input**

You need to exchange the `TriggerGoogleSignIn` event for whatever event you want to use to trigger the login flow. Remove the node `REPLACE: Google ID Token` and replace it with your preferred method of getting the [Google Identity Token](https://cloud.google.com/docs/authentication/get-id-token).

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success. For subsequent calls to different LootLocker methods you will want to create variables from the Player Id and Player Identifier outputs. In the session response when starting an Apple session you also get a refresh token that we will save under the hood. But if you want to use it manually, we provide it for you.

Beyond those three, there is a lot of interesting stuff in the session response, like current xp and level for example.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/google" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"id_token\": \"eyJQa....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

#### Refresh Session

With Google authentication, you can refresh the session instead of having the player do a full sign-in every time. The request will return a 401 (Unauthorized) if the refresh token has expired, in which case you will need to start a new session.

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

```csharp
LootLockerSDKManager.RefreshGoogleSession((response) =>
{
    if (!response.success)
    {
        if (response.statusCode == 401) {
            // Refresh token has expired, use StartGoogleSession
        }
        else {
            Debug.Log("error starting LootLocker session");
        }

        return;
    }

    Debug.Log("session started successfully");
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/BzVC2rkhpwTZfEMN9eaZ" alt="Blueprint example of refreshing a game session with Google - https://blueprintue.com/blueprint/45akd39v/"><figcaption><p><a href="https://blueprintue.com/blueprint/45akd39v/">Blueprint example of refreshing a game session with Google</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/45akd39v/).

**Input**

You need to exchange the `TriggerSessionRefresh` event for whatever event you want to use to trigger the login flow. The node `Refresh Token` is the one you saved [when starting a session](#start-session), but it is optional as we save it behind the scenes as well.

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success. For subsequent calls to different LootLocker methods you will want to create variables from the Player Id and Player Identifier outputs. In the session response when starting an Apple session you also get a refresh token that we will save under the hood. But if you want to use it manually, we provide it for you.

Beyond those three, there is a lot of interesting stuff in the session response, like current xp and level for example.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/google" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"refresh_token\": \"748b....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Sign in with Google! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Google Play Games

This how-to walks you through authenticating and setting up Google Play Games for your game.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [An existing game in the Web Console](https://console.lootlocker.com/)
* [Google Play Games Platform enabled in the Web Console](https://console.lootlocker.com/settings/platforms/google_play_games)
* [An account and game setup in the Google Play Developer Console](https://play.google.com/console/developers)
* [Play Games Services setup for your game](https://developer.android.com/games/pgs/console/setup)

### Configure Google Play Games

First, navigate to the [Platform Settings for Google Play Games](https://console.lootlocker.com/settings/platforms/google_play_games) in the LootLocker Web Console.

#### Client ID

In the Google Play Developer Console, navigate to **Play Games Services** -> **Setup and management** -> **Configuration** and copy the **Client ID** and paste it into the **Client ID** field in the LootLocker Web Console.

#### Client Secret

Go to the [Google Cloud Console](https://console.cloud.google.com/auth/clients), navigate to the OAuth2-part of your app and generate a new secret key or create a new Oauth client with a **Web Client** configuration. Download the Client Secret and store it in a safe place, copy the Client Secret and paste it into the **Client Secret** field in the LootLocker Web Console.

#### App ID

In the Google Play Developer Console, navigate to **Play Games Services** -> **Setup and management** -> **Configuration** and copy the **Project ID** and paste it into the **App ID** field in the LootLocker Web Console.

After all credentials have been added, click **Save** in the LootLocker Web Console. Google Play Games is now fully configured in the Web Console and ready to be used in your game.

#### Web Console Setup

Now, verify that all information that is in their respective input fields.

* [Client ID](#client-id)
* [Client Secret](#client-secret)
* [App ID](#app-id)

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

### Authenticating with Google Play Games in your game

LootLocker supports [Google Play Games v2](https://developer.android.com/games/pgs/start), if your game is running v1 of Google Play Games, you must [migrate](https://developer.android.com/games/pgs/migration_overview) to v2.

#### Starting a session

When signing in with Google Play Games, you need to add the `OPEN_ID`-scope when requesting the token.

{% tabs %}
{% tab title="Unity" %}
Make sure that you have the Google Play Games v2 SDK properly installed and setup before adding any code.

This code serves as an example, make sure to add any error handling that your game might require.

```csharp
using UnityEngine;
using GooglePlayGames;
using GooglePlayGames.BasicApi;
using LootLocker.Requests;
using System.Collections.Generic;

public class GoogleSignIn : MonoBehaviour
{
    // Example of activating Google Play Games and starting a session with LootLocker
    public void Start()
    {
        PlayGamesPlatform.Activate();

        PlayGamesPlatform.Instance.Authenticate((response) =>
        {
            if (response == GooglePlayGames.BasicApi.SignInStatus.Success)
            {
                // Add desired scopes for server-side access
                List<AuthScope> scopes = new List<AuthScope>
                {
                    AuthScope.OPEN_ID
                };

                PlayGamesPlatform.Instance.RequestServerSideAccess(true, scopes, (google) =>
                {
                    if (google != null)
                    {
                        string token = google.GetAuthCode();
                        LootLockerSDKManager.StartGooglePlayGamesSession(token, (response) =>
                        {
                            if (response.success)
                            {
                                Debug.Log("Successfully started LootLocker session with Google Play Games.");
                            }
                            else
                            {
                                Debug.Log("LootLocker Google Play Games session failed: " + response.Error);
                            }
                        });
                    }
                    else
                    {
                        Debug.Log("Google server-side access request failed.");
                    }
                });
            }
            else
            {
                Debug.Log("Google Play Games authentication failed: " + response);
            }
        });
    }
}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon
{% endtab %}

{% tab title="Unreal C++" %}
Coming soon
{% endtab %}

{% tab title="REST" %}

```bash
curl --location --request POST 'https://api.lootlocker.com/game/session/google-play-games/v1/login' \
--header 'Content-Type: application/json' \
--data-raw '{
    "game_api_key": "string",
    "auth_code": "string",
    "game_version": "string",
    "optionals": {
        "timezone": "string",
        "player_name": "string"
    }
}'
```

Example response:

```json
{
    "session_token": "string",
    "player_identifier": "string",
    "player_id": 0,
    "player_name": "string",
    "player_ulid": "string",
    "player_created_at": "string",
    "public_uid": "string",
    "seen_before": true,
    "check_grant_notifications": true,
    "check_deactivation_notifications": true,
    "check_dlcs": [
        "string"
    ],
    "success": true
}
```

{% endtab %}
{% endtabs %}

### Conclusion

In this how-to we have set up and enabled Google Play Games. Your game is now fully setup to integrate any of our other available [features](/the-basics/core-concepts).


# Epic Games

{% hint style="info" %}
The Epic Games platform needs to be enabled and configured in the Web Console before it can be used in your game.
{% endhint %}

{% hint style="success" %}
Both **Epic Games Sign In** and **Epic Games Connect** are supported.
{% endhint %}

#### Configuration

To start using Sign in with Epic Games we first need to configure our integration in the [platform settings](https://console.lootlocker.com/settings/platforms/google_sign_in).

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

**Client ID**

Client ID can be retrieved from the [Epic Dev Console](https://dev.epicgames.com/portal/). If you don't have an Client, you will have to create one, more info here: <https://dev.epicgames.com/docs/dev-portal/client-credentials>

#### Start Session

You need to acquire a [Epic Identity Token](https://dev.epicgames.com/docs/game-services/eos-connect-interface#retrieving-an-id-token-for-user) to be able to start an Epic Games session, but once you have that, simply follow along to the code snippet below. In the response from LootLocker you will find a refresh token which can be used to refresh the session so that you do not need to do the full sign-in flow each time.

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

```csharp
string idToken = "eyJhbGciOiJSUz............";
LootLockerSDKManager.StartEpicSession(idToken, (response) =>
{
    if (!response.success)
    {
        Debug.Log("error starting LootLocker session");

        return;
    }

    Debug.Log("session started successfully");

    // Store these if you want to manually supply it to refresh the session without using the full sign in flow
    string refreshToken = response.refresh_token;
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/8NzT7JLeO2wb0MpoxqwI" alt="Blueprint example of starting a game session with Epic - https://blueprintue.com/blueprint/ghx23e2z/"><figcaption><p><a href="https://blueprintue.com/blueprint/ghx23e2z/">Blueprint example of starting a game session with Epic</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/ghx23e2z/).

**Input**

You need to exchange the `TriggerEpicSignIn` event for whatever event you want to use to trigger the login flow. Remove the node `REPLACE: Epic ID Token` and replace it with your preferred method of getting the [Epic Identity Token](https://dev.epicgames.com/docs/game-services/eos-connect-interface#retrieving-an-id-token-for-user).

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success. For subsequent calls to different LootLocker methods, you will want to create variables from the Player Id and Player Identifier outputs. In the session response when starting an Apple session you also get a refresh token that we will save under the hood. But if you want to use it manually, we provide it for you.

Beyond those three, there is a lot of interesting stuff in the session response, like current xp and level for example.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/epic" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"id_token\": \"eyJQa....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

#### Refresh Session

With Epic Games authentication, you can refresh the session instead of having the player do a full sign-in every time. The request will return a 401 (Unauthorized) if the refresh token has expired, in which case you will need to start a new session.

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

```csharp
LootLockerSDKManager.RefreshEpicSession((response) =>
{
    if (!response.success)
    {
        if (response.statusCode == 401) {
            // Refresh token has expired, use StartEpicGamesSession
        }
        else {
            Debug.Log("error starting LootLocker session");
        }

        return;
    }

    Debug.Log("session started successfully");
    
    // Store these if you want to manually supply it to refresh the session without using the full sign in flow
    string refreshToken = response.refresh_token;
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/vIzCoZVOA3YM8sA06Yhl" alt="Blueprint example of refreshing a game session with Epic - https://blueprintue.com/blueprint/mhp0lgb-/"><figcaption><p><a href="https://blueprintue.com/blueprint/mhp0lgb-/">Blueprint example of refreshing a game session with Epic</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/mhp0lgb-/).

**Input**

You need to exchange the `TriggerSessionRefresh` event for whatever event you want to use to trigger the login flow. The node `Refresh Token` is the one you saved [when starting a session](#start-session), but it is optional as we save it behind the scenes as well.

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success. For subsequent calls to different LootLocker methods, you will want to create variables from the Player Id and Player Identifier outputs. In the session response when starting an Apple session you also get a refresh token that we will save under the hood. But if you want to use it manually, we provide it for you.

Beyond those three, there is a lot of interesting stuff in the session response, like current xp and level for example.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/epic" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"refresh_token\": \"748b....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Sign in with Epic Games! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# PlayStation

{% hint style="danger" %}
**We are only able to provide support for this integration if you are a registered PlayStation Partner developer. If you are,** [**get in touch**](mailto:hello@lootlocker.com?subject=PlayStation%20Integration%20Support)**.**
{% endhint %}


# Meta / Oculus

{% hint style="info" %}
The Meta / Oculus platform needs to be enabled and configured in the Web Console before it can be used in your game.
{% endhint %}

#### Configuration

To start using Meta / Oculus sign in we first need to configure our integration in the [platform settings](https://console.lootlocker.com/settings/platforms/meta).

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

**Meta App ID / App Secret**

Meta App ID and Meta App Secret can be retrieved from the [Meta Dashboard](https://developer.oculus.com/manage) in the API section for your app.

#### Start Session

You will need to fetch the UserID and Nonce using the Oculus SDK provided by Meta, more information can be found here:

[Unity](https://docs.unity.com/authentication/en-us/manual/platform-signin-oculus)

[Unreal](https://developer.oculus.com/reference/platform/v55/o_v_r_requests_user_8h/#a2c6a612f691e1009621c21d17b48c8d3)

Next simply follow along to the code snippet below passing in the UserId and Nonce to LootLocker. In the response from LootLocker you will find a refresh token which can be used to refresh the session so that you do not need to do the full sign-in flow each time.

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

```csharp
string userID = "user_id";
string nonce = "nonce";

LootLockerSDKManager.StartMetaSession(userID, nonce, (response) =>
{
    if (!response.success)
    {
        Debug.Log("error starting LootLocker session");

        return;
    }

    Debug.Log("session started successfully");

    // Store these to be able to refresh the session without using the full sign in flow
    string refreshToken = response.refresh_token;
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/fmsIegNrVB9cNu4oqYqs" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/_dqkgzpr/">Blueprint example of starting a Meta Session</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/meta" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"user_id\": \"1234567890\", \"nonce\": \"0987654321\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

#### Refresh Session

With Meta authentication, you can refresh the session instead of having the player do a full sign-in every time. The request will return a 401 (Unauthorized) if the refresh token has expired, in which case you will need to start a new session.

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

```csharp
LootLockerSDKManager.RefreshMetaSession((response) =>
{
    if (!response.success)
    {
        if (response.statusCode == 401) {
            // Refresh token has expired, use StartMetaSession
        }
        else {
            Debug.Log("error starting LootLocker session");
        }

        return;
    }

    Debug.Log("session started successfully");
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/GOyHISYVvV0ieONfpYwS" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/_q87xto3/">Blueprint example of Refreshing a session</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/meta" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"refresh_token\": \"748b....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Meta / Oculus authentication! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Xbox

{% hint style="danger" %}
**We are only able to provide support for this integration if you are a registered ID\@Xbox developer. If you are,** [**get in touch.**](mailto:hello@lootlocker.com?subject=Xbox%20Integration%20Support)
{% endhint %}


# Nintendo Switch

We have made authenticating with Nintendo Switch as simple as possible with this integration.

#### Register a Session

Ensure that you have the following conditions met:

* You are registered with Nintendo as a developer (otherwise you will not have access to their developer tools)
* Install the Nintendo Dev Interface (obtained from [developer.nintendo.com](https://developer.nintendo.com)) and set up an environment that matches your Unity version
* Install the NintendoSDK from the environment, as well as all dependencies
* You have enabled the Nintendo Switch platform in your games settings in LootLocker, and the Nintendo Application ID matches in your editor and the LootLocker console (You can use the Nintendo default one during initial development)

{% hint style="info" %}
Note that we can not guide you on how to obtain the `NSAIDToken`for the example below due to legal reasons. The NintendoSDK does have great examples on how to obtain this yourself though.
{% endhint %}

{% tabs %}
{% tab title="Unity" %}
{% hint style="warning" %}
This requires that you have [set up the Unity SDK in your game.](/the-basics/unity-quick-start)
{% endhint %}

We'll start by creating a new empty Game Object in your scene and calling it GameManager. Feel free to skip this if you already have a GameManager or similar in your game.

![](/files/IInMmSCjZSq8F3wTAJq5)

Then in this new GameObject you can add a new script called GameManager

![](/files/owQYNpNSIHeKP2fB39qn)

Open up your new script in your editor of choice and add the following code.

```csharp
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using LootLocker.Requests;

public class GameManager : MonoBehaviour
{
    void Start()
    {
        string NSAIDToken = getNSAIDToken();
        LootLockerSDKManager.StartNintendoSwitchSession(NSAIDToken, (response) =>
        {
            if (!response.success)
            {
                Debug.Log("error starting LootLocker session");

                return;
            }

            Debug.Log("successfully started LootLocker session");
        });
    }
}
```

To confirm that everything is running without errors, you can start your game and check the console for the correct Debug.Log message.
{% endtab %}

{% tab title="Unreal" %}
​

<figure><img src="/files/5g5BeP66stSlJdCwO31Y" alt="Blueprint example of starting a session with a Nintendo Switch Player - https://blueprintue.com/blueprint/e190bbsq/"><figcaption><p>​<a href="https://blueprintue.com/blueprint/e190bbsq/">Blueprint example of starting a session with a Nintendo Switch Player</a>​</p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/e190bbsq/).

**Input**

You need to exchange the `TriggerNintendoStartSession` event for whatever event you want to use to trigger the login flow.

Remove the node `REPLACE: NSA Id Token` so that you provide the actual Nintendo Switch Token.

**Output**

We recommend branching the completed events on the success flag, and if you do this you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/nintendo-switch" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"nsa_id_token\": \"eyJQa....\", \"game_version\": \"1.0.0.0\", \"session_id\": \"8438f4b3-90e4-4cdd-8434-c5c6b4c2c9f1\"}"
```

Example response:

```json
{
  "success": true,
  "session_token": "e6fa44946f077dd9fe67311ab3f188c596df9969",
  "player_id": 3,
  "public_uid": "TSEYDXD8",
  "player_created_at": "2022-05-30T07:56:01+00:00",
  "check_grant_notifications": true,
  "check_deactivation_notifications": false,
  "seen_before": true
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Nintendo Switch! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Discord

{% hint style="info" %}
The Discord platform needs to be enabled and configured in the Web Console before it can be used in your game.
{% endhint %}

#### Configuration

To start using Sign in with Discord we first need to configure our integration in the [platform settings](https://console.lootlocker.com/settings/platforms/discord).

<figure><img src="/files/fmeFcM5EaTLqwYncELjO" alt="Discord platform settings"><figcaption></figcaption></figure>

**Client ID & Client Secret**

Client ID can be retrieved from the [Discord Developer Portal](https://discord.com/developers/applications). If you don't have an Application, you will have to create.

#### Start Session

You need to acquire either a [Discord Access Token](https://discord.com/developers/docs/topics/oauth2#authorization-code-grant-access-token-exchange-example) or a Discord Refresh Token (also returned from the Access Token Exchange) to be able to start a Discord session, but once you have that, simply follow along to the code snippet below. In the response from LootLocker you will find a refresh token which can be used to refresh the session so that you do not need to do the full sign-in flow each time.

{% tabs %}
{% tab title="Unity" %}
coming soon
{% endtab %}

{% tab title="Unreal" %}
coming soon
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/discord" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"access_token\": \"eyJQa....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

#### Refresh Session

With Discord authentication, you can refresh the session instead of having the player do a full sign-in every time. The request will return a 401 (Unauthorized) if the refresh token has expired, in which case you will need to start a new session.

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

{% endtab %}

{% tab title="Unreal" %}

{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/discord" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"refresh_token\": \"eyJQa....\", \"game_version\": \"1.0.0.0\" }"
```

Example response:

```json
{
  "session_token": "eb13a7bf17efg36cb8481a8ds18809c7e85686e6",
  "player_id": 9358,
  "public_uid": "J72YP6MS",
  "player_name": null,
  "player_created_at": "2023-02-21T15:33:25+00:00",
  "check_grant_notifications": false,
  "check_deactivation_notifications": false,
  "seen_before": true,
  "refresh_token": "eyJh............................."
}
```

{% endtab %}
{% endtabs %}

{% hint style="success" %}
Congratulations - you have now started using LootLocker in your game with Sign in with Discord! Next up we suggest you look at our [feature set,](/the-basics/what-is-lootlocker#overview) and decide which ones you want to use in your game.
{% endhint %}


# Broadcasts

Broadcasts let you schedule and deliver localized messages to targeted players across one or more games within your LootLocker organization. Broadcasts can be sent to all players or limited to specific player segments, allowing you to tailor messages based on platform, activity, playtime, or other player criteria.

Each broadcast can include text, images, actions, and optional key–value pairs, giving you full control over how messages are displayed and what they trigger in-game. Broadcasts support full localization and flexible publication schedules across multiple timezones, ensuring players receive the right message at the right time in their preferred language.

Use Broadcasts to share news, updates, and promotions, notify players of outages, publish changelogs, highlight in-game events, or communicate across your catalog, all while keeping the experience consistent across games, regions, and platforms.

### Use Cases

* Share news and game updates
* Promote other games in your catalog
* Notify players of server outages
* Publish changelogs and patch notes
* Announce events and special activities
* Highlight game store updates and offers

### How-to

* [Configure Broadcasts in Web Console](/players/broadcasts/how-to/configure-broadcasts-in-web-console)
* [List Broadcasts In-Game](/players/broadcasts/how-to/list-broadcasts-in-game)


# How To


# Configure Broadcasts in Web Console

In this how-to guide, we will create, configure, and edit a broadcast targeted towards a catalog of games with several scheduled publication times and multiple localized content.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [At least 1 created game in the web console](https://docs.lootlocker.com/)

### Broadcast Menu

You’ll find Broadcasts under Player Operations in the Web Console sidebar. From here, you can:

* View a summary of existing broadcasts
* Create new broadcasts
* Edit or delete existing broadcasts

Each broadcast entry shows:

* Broadcast name (only used for internal identification)
* Target game(s) (select from all games within your LootLocker organization)
* Localized language(s)
* Number of publication schedules, with their status (live, pending, expired)

Clicking a broadcast expands its details, including the Broadcast ID (used in API calls) and full publication schedule information.

![](/files/Wwze6drzcTnXkDlOt8L2)

### Creating a Broadcast

To create a new broadcast, click Create New Broadcast.

![](/files/wFEnfeeJuN8BDTC56xQ6)

You’ll be asked to provide:

* **Name (Required):** Internal name for the broadcast (not visible to players).
* **Target Games (Required):** Select which game(s) will receive the broadcast. At least 1 game must be selected.
* **Publication Settings:** Set at least one publication schedule with a start date (required) and optional end date. The default schedule must be in UTC (all players default to UTC if no timezone is set). Learn more about creating additional schedules in other timezones below.
* **Content & Localizations:** Enter the title, body text, image URL, actions, or any custom key-values to be included in the broadcast package.

#### Adding Basic Information

Each broadcast requires a name. This name is only for internal use within LootLocker and does not get included in the API response.

![](/files/7WTHgTLRKWYabBNZ1Bfv)

#### Selecting Target Games

Use the dropdown and search bar to select one or more games that this broadcast will target.

![](/files/5nkLz687gvS7oGjNgDZ8)

#### Adding Player Segments

Player Segments let you target Broadcasts to specific groups of players based on their behavior, activity, or platform usage. To view a Broadcast, a player must qualify for all conditions within a single Segment. If multiple Segments are added to a Broadcast, they are evaluated as an OR condition. This means a player only needs to qualify for one of the attached Segments to receive the Broadcast message.

![](/files/AV89jvAO5jhM9uGO3gcN)

To add a Segment, click Add Segment.

To learn more about configuring Segments, see the [Segments](/shared-systems/player-segments) documentation.

#### Managing Publication Settings

Click Add Schedule to create additional schedules with start, end, and timezone settings. Enter a Start and (optionally) End times for when your broadcast will be live. Use the quick time presets to quickly populate a start time and an end time relative to the start time.

Click the red Delete button to remove a schedule.

Note: at least one schedule in UTC must always be defined.

![](/files/PqvJaTKHmU6x32e1KmPP)

#### Adding Content & Additional Languages

Each broadcast contains the content sent to the game. English is required by default, but you can add as many additional languages as you need. Learn more about managing additional languages below.

![](/files/UTSVCSOhGWrZ8g2PnZkK)

Fields include:

* **Language (Required):** The language of the content.
* **Image URL:** A link to an image displayed with the broadcast.
* **Headline (Required):** Title of the broadcast.
* **Body (Required):** Main text of the broadcast.
* **Action:** An associated action triggered with the broadcast.
* **Custom Fields:** Optional key–value pairs for additional data. Learn more about Custom Fields below.

When ready, click **Create Broadcast** to save.

#### Managing Languages

Click Add Language to create a new language entry. Each language includes its own set of content fields.

Use the Delete button to remove a language.

#### Managing Custom Fields

You can attach custom key–value pairs to any broadcast. Click Add Field to create a new pair.

Enter the key and value, then click Add Field again to save.

![](/files/g0sT7I2yo2zp12nfULSI)

#### Discord Settings

Broadcasts can also be pushed to a Discord server by using our [Discord Bot Squire](broken://pages/cTeQ0GAw9pKN3qKObw6Q).

Check the Publish to Discord option to publish this broadcast to the configured Discord channel at the earliest publication schedule.

Use the Custom Embed Message to enter different copy to be published to Discord.

![](/files/sTWKT3secuniHYJVblXT)

### Editing a Broadcast

To edit an existing broadcast, click Edit in the Broadcast menu.

#### Target Games

Click Edit Games, update the game list, and click Save Changes.

![](/files/N8va9CB0CPl4k8VOZUdw)

#### Publication Schedules

Click Edit Schedules, make your changes, and click Save Changes.

![](/files/Mbw2ruYdSNSqW80jVw4S)

#### Content & Languages

The Content & Languages section lists all created languages and the number of content fields in each.

![](/files/x1ZsL7dG4kndujwgnDQT)

Click a language to expand its content. Here, you can edit any key or value directly. Keep in mind that system keys (marked Reserved) cannot be edited.

Use Add Localization to add new key–value fields to a language.

Use Add Languages to add an entirely new language. Fill in the fields and click Save New Languages.

#### Bulk Actions

If you have more than one language, the Bulk Actions options will become available. Here, you can use the Add Key to All Languages to easily add a key-value pair to all languages.

![](/files/jYmvHzCmI2NV1cSnmVzy)

### Conclusion

In this how-to, we’ve created a broadcast that targets one or more games, has multiple publication times and supports several languages. Now that you are familiar with these systems, it's time to [list broadcasts in your game](/players/broadcasts/how-to/list-broadcasts-in-game).


# List Broadcasts In-Game

In this How-to, we will start a session with the users timezone, retrieve the latest broadcast messages and display the data in your engine.

## Prerequisites

* [A LootLocker account and a created game](https://lootlocker.com/sign-up)
* [The LootLocker SDK configured and implemented in your game](/the-basics/sdks)
* [Configured broadcasts in the Web Console](https://docs.lootlocker.com/players/broadcasts/how-to/configure-broadcasts-in-web-console)

## Start Session with Timezone Support (Optional)

To ensure broadcast messages are filtered and scheduled according to the players timezone, you can optionally include timezone information when starting a player session.

In this How-To, we are using [Guest Login](/players/authentication/how-to/guest-login), but [all platforms](/players/authentication) are supported.

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

```csharp
using LootLocker.Requests;
using System;

public void StartSessionWithTimezone()
{
    // Create session optionals to include timezone
    LootLockerSessionOptionals optionals = new LootLockerSessionOptionals();
    
    // For Windows, convert the system timezone to IANA format, read more in the section below
    optionals.timezone = LootLockerTimezoneConverter.ConvertWindowsToIanaTzString(TimeZoneInfo.Local.StandardName);
    
    // Start a guest session with timezone
    LootLockerSDKManager.StartGuestSession((response) =>
    {
        if (response.success)
        {
            Debug.Log("Session started successfully with timezone: " + options.timezone);
            // Proceed to retrieve broadcasts
        }
        else
        {
            Debug.LogError("Failed to start session: " + response.errorData.message);
        }
    }, optionals);
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
#include "LootLockerSDKManager.h"

void YourClass::StartSessionWithTimezone()
{
	FLootLockerSessionOptionals optionals {
		YourClass::GetTimezone()
	};
	ULootLockerSDKManager::GuestLogin(FLootLockerSessionResponse::CreateLambda([](FLootLockerAuthenticationResponse Response) {
		if (Response.success) {
			UE_LOG(LogWorkingProject, Display, TEXT("Session started successfully with timezone! Player ULID: %s"), *Response.player_ulid);
		} else {
			UE_LOG(LogWorkingProject, Error, TEXT("Failed to start session with timezone: %s"), *Response.ErrorData.Message);
		}
	}), "", optionals);
}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/0E6ljhYtiOv7n9X0poCR" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/5i2h8m-5/">Blueprint example of starting a LootLocker session with a timezone optional</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

### Coming Soon

{% endtab %}
{% endtabs %}

## About Timezones

LootLocker uses the [Iana](https://www.iana.org/time-zones) standard for dealing with timezones. A timezone is written to a players [metadata](/shared-systems/metadata) with the key `ll.timezone`.

All platforms *except* Windows uses [Iana](https://www.iana.org/time-zones). If your game is running on Windows, you **must** convert the Windows timezone to an Iana timezone before sending it to LootLocker.

{% tabs %}
{% tab title="Unity" %}
Use the utility-class `LootLockerTimezoneConverter` to convert back and forth between Iana timezones

```csharp
    // When sending a Windows Timezone to LootLocker:
        LootLockerTimezoneConverter.TryConvertStringToIanaTzString(TimeZoneInfo.Local.StandardName);
    // When reading an Iana timezone returned from LootLocker:
        LootLockerTimezoneConverter.TryConvertStringToWindowsTzString(/*LootLockerIanaTimezoneReturnedFromMetadata*/);
```

{% endtab %}

{% tab title="Unreal C++" %}
Every time you start a LootLocker session, send the Iana timezone for the user. One way to do this is to calculate the offset from UTC and format it as an Iana timezone:

```cpp
FString YourClass::GetCurrentUTCOffsetAsIanaTimezone()
{
	FDateTime Now = FDateTime::Now();
	FDateTime UTCNow = FDateTime::UtcNow();
	FTimespan Offset = Now - UTCNow;
	int offsetHours = Offset.GetHours();
	FString tz = FString::Printf(TEXT("ETC/GMT%s%d"), offsetHours < 0 ? TEXT("-") : TEXT("+"), FMath::Abs(offsetHours));
	return tz.IsEmpty() ? TEXT("N/A") : tz;
}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Every time you start a LootLocker session, send the Iana timezone for the user. One way to do this is to calculate the offset from UTC and format it as an Iana timezone:

<figure><img src="/files/cEAojfWs1f8NeQtTWCQM" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/n2gmd0v3/">Blueprint example of creating an Iana offset timezone code from calculated UTC offset</a></p></figcaption></figure>
{% endtab %}
{% endtabs %}

## Retrieve Broadcast Messages

Once you have an active session, you can retrieve broadcast messages that are currently live for your game and display the information in regards to the users preferred language,

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

```csharp
using LootLocker.Requests;
using System.Collections.Generic;

public void GetBroadcastMessages()
{
    string[] languagesToRetrieve = new string[] { "en", "sv" }; // English and Swedish
    int limit = 10; // Number of items to fetch in this request
    
    LootLockerSDKManager.ListBroadcasts(languagesToRetrieve, limit, (response) =>
    {
        if (response.success)
        {
            Debug.Log($"Retrieved {response.broadcasts.Length} broadcast messages");
            
            foreach (var broadcast in response.broadcasts)
            {
                ProcessBroadcast(broadcast);
            }
        }
        else
        {
            Debug.LogError("Failed to retrieve broadcasts: " + response.errorData.message);
        }
    });
}

// Example function for processing the broadcast
private void ProcessBroadcast(BroadcastMessage broadcast)
{
    // If you need to treat languages differently, you can check the key of the current language
    if (broadcast.languages.ContainsKey("en"))
    {
        LootLockerBroadcastLanguage currentLanguage = broadcast.languages["en"];
        // The headline of the broadcast
        Debug.Log($"Headline: {currentLanguage.headline}");
        // The body of the broadcast
        Debug.Log($"Body: {currentLanguage.body}");
        // The language code of the broadcast
        Debug.Log($"Language: {currentLanguage.language_code}");
        // An URL to an image, add your own logic to download and display images in your UI
        Debug.Log($"Image URL: {currentLanguage.image_url}");
        // Custom action to take, for example starting a specific animation or instantiate a certain prefab
        Debug.Log($"Action: {currentLanguage.action}");
        // Process additional logic for extra localization keys, if any
        foreach (var extraLocalization in currentLanguage.localizations)
        {
            Debug.Log($"Additional Localization - Key: {extraLocalization.Key}, Value: {extraLocalization.Value}");
        }
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
#include "LootLockerSDKManager.h"

void YourClass::ListBroadcasts()
{
	TArray<FString> LanguagesToRetrieve { "en", "sv" }; // English and Swedish
	int Limit = 10; // Number of items to fetch in this request
    
	ULootLockerSDKManager::ListBroadcasts(LanguagesToRetrieve, Limit, FLootLockerListBroadcastsResponseDelegate::CreateLambda([](const FLootLockerListBroadcastsResponse& Response)
    {
        if (Response.success)
        {
            for (const FLootLockerBroadcast& broadcast : Response.broadcasts)
            {

                UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Broadcast ID: %s"), *broadcast.id);
                UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Broadcast Name: %s"), *broadcast.name);
                UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Game Name: %s"), *broadcast.game_name);
                UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Publication information:"));
                for(const FLootLockerBroadcastPublicationSetting& publicationSettings : broadcast.publication_settings)
                {
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("    Publication Setting ID: %d"), publicationSettings.id);
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("    Publication start date: %s"), *publicationSettings.start.ToString());
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("    Publication end date: %s"), *publicationSettings.end.ToString());
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("    Publication timezone: %s"), *publicationSettings.tz);
                }

                // If you need to treat languages differently, you can check the key of the current language
                if(broadcast.languages.Contains("en")) {
                    const FLootLockerBroadcastLanguage& currentLanguage = broadcast.languages["en"];
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Headline: %s"), *currentLanguage.headline);
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Body: %s"), *currentLanguage.body);
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Language code: %s"), *currentLanguage.language_code);
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Image URL: %s"), *currentLanguage.image_url);
                    UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Action: %s"), *currentLanguage.action);
                    for(const auto& extraLocalization : currentLanguage.localizations) {
                        UE_LOG(YOUR_LOG_CATEGORY, Display, TEXT("Additional Localization - Key: %s, Value: %s"), *extraLocalization.Key, *extraLocalization.Value);
                    }
                }
            }
        }
        else
        {
            UE_LOG(YOUR_LOG_CATEGORY, Error, TEXT("ListBroadcasts failed: %s"), *Response.ErrorData.Message);
        }
    }));
}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/hNwLY5aaPqdBesyo3TG8" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/a5_5dmom/">Blueprint example of listing Broadcasts</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

### Coming Soon

{% endtab %}
{% endtabs %}

## Conclusion

In this How-to, we successfully completed the task of retrieving broadcast messages from LootLocker and have displayed their information.

With this implementation, your game can now retrieve rich, localized broadcast messages that are properly scheduled according to player timezones. The multiple localization support allows you to access content in different languages and include custom localized fields like button text, subtitles, and other game-specific content.


# Files

The Files feature in LootLocker allows you to store files within a player’s profile, ideal for larger files that benefit from the speed and scalability of a content delivery network (CDN). For smaller data storage needs, we recommend using [Player Metadata](/shared-systems/metadata).

With Files, you can upload, update, download, and delete files via API or through the LootLocker Web Console. Each file requires a specified purpose upon upload, and files can be marked as private to restrict visibility from other players. The Files feature also supports up to five revisions per file, with the option to revert to previous versions via the Web Console.

### Use Cases

* Player Profile Picture
* Cloud Saves
* Screenshots

### How-to

* [Configure Files in Web Console](/players/files/how-to/manage-player-files)
* [Work with Files In-Game](/players/files/how-to/work-with-files)


# How To


# Manage Files in Web Console

To view Player files in the web console, go to the player and select the files tab. From here it's possible to view, download, upload or delete the player files.

![](/files/S2jHCRlfokEGnRaxfaXu)


# Work with Files In-Game

### Retrieving Player Files

#### Retrieve All Player Files

Retrieve all files associated with the player.

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

```csharp
LootLockerSDKManager.GetAllPlayerFiles((response) =>
{
    if (response.success)
    {
        Debug.Log("Successfully retrieved player files: " + response.items.Length);
    } 
    else
    {
        Debug.Log("Error retrieving player storage");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/r6y4mnhJuvBB82MOIXoq" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/jr6b9s4s/">Blueprint Example of retrieving all player files</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X GET "https://api.lootlocker.io/game/player/files" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01"
```

Example response:

```json
{
  "items": [
    {
      "id": 318,
      "revision_id": "01GPNZHCVEF6F0QGA3V2B7CN44",
      "name": "smol.jpg",
      "size": 7056,
      "purpose": "SAVE_GAME",
      "public": false,
      "url": "https://cdn.lootlocker.io/2/892/oJnNPGsiuzytMOJPatwtPilfsfykSBGp.jpg?Expires=1635369933&Signature=Wp-PUZdoh3XdqQNOnPP80-q81ftGpLVJ3oub3RajnfjaP15rHzVWCCJ8e8lzpzrzdilu1gxoTiK1NT7iZayJXV~1XvcagCbkLCsrLkHlDZ0ms17C1YL5iNu1crBCJOhOhpwhknDmgt95NWa2GjLydylj7-0mqjALoURAMzpWvmbXUClUhtzIgGsBIEn2jy~syplWtMstTHE13EHmsxqfbAr8fkp91JGHaLFqSJL5YvlnIe5WoVNeBII8tjBb6YtEEF2dyliLyJOk8RaGtCDodKMmwOlwAlw-~lyfPrUOxH62c87-vkn8~uPQgAxcX0kp6NDudFrO5uiyESyOymIscw__&Key-Pair-Id=APKAIIBA3IODTCVA4BKQ",
      "url_expires_at": "2021-10-27T21:25:33.339117882Z",
      "created_at": "2021-10-27T14:20:31Z"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

#### Retrieve All Public Files From Another Player

Retrieve all (public) files associated with another player.

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

```csharp
int playerId = 1;

LootLockerSDKManager.GetAllPlayerFiles(playerId, (response) =>
{
    if (response.success)
    {
        Debug.Log("Successfully retrieved player files: " + response.items.Length);
    } 
    else
    {
        Debug.Log("Error retrieving player storage");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/Zp5eBo3n47BMC8GU8NMS" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/k3hhzja1/">Blueprint example of retrieving all files from another player marked public</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X GET "https://api.lootlocker.io/game/player/{player_id}/files" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01"
```

Example response:

```json
{
  "items": [
    {
      "id": 5318,
      "revision_id": "01GPNZHCVEF6F0QGA3V2B7CN44",
      "name": "world.jpg",
      "size": 7056,
      "purpose": "WORLD",
      "public": true,
      "url": "https://cdn.lootlocker.io/2/892/oJnNPGsiuzytMOJPatwtPilfsfykSBGp.jpg?Expires=1635369933&Signature=Wp-PUZdoh3XdqQNOnPP80-q81ftGpLVJ3oub3RajnfjaP15rHzVWCCJ8e8lzpzrzdilu1gxoTiK1NT7iZayJXV~1XvcagCbkLCsrLkHlDZ0ms17C1YL5iNu1crBCJOhOhpwhknDmgt95NWa2GjLydylj7-0mqjALoURAMzpWvmbXUClUhtzIgGsBIEn2jy~syplWtMstTHE13EHmsxqfbAr8fkp91JGHaLFqSJL5YvlnIe5WoVNeBII8tjBb6YtEEF2dyliLyJOk8RaGtCDodKMmwOlwAlw-~lyfPrUOxH62c87-vkn8~uPQgAxcX0kp6NDudFrO5uiyESyOymIscw__&Key-Pair-Id=APKAIIBA3IODTCVA4BKQ",
      "url_expires_at": "2022-06-27T21:25:33.339117882Z",
      "created_at": "2022-06-27T14:20:31Z"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

#### Retrieve Single Player File

Retrieve a specific player file.

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

```csharp
int playerFileId = 1;
LootLockerSDKManager.GetPlayerFile(playerFileId, (response) =>
{
    if (response.success)
    {
        Debug.Log("Successfully retrieved player file with id: " + response.id);
        
    } 
    else
    {
        Debug.Log("Error retrieving player file");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/LHznsPyTxPHCsPZUicfI" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/enoyu93i/">Blueprint example for retrieving a specific player file</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X GET "https://api.lootlocker.io/game/player/files/{file_id}" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01"
```

example response:

```json
{
  "id": 318,
  "revision_id": "01GPNZHCVEF6F0QGA3V2B7CN44",
  "name": "smol.jpg",
  "size": 7056,
  "purpose": "SAVE_GAME",
  "public": false,
  "url": "https://cdn.lootlocker.io/2/892/oJnNPGsiuzytMOJPatwtPilfsfykSBGp.jpg?Expires=1635370000&Signature=JKJTvRoZpxgtebwCqtuADp-mH4j958vxo29LvRtWv~VC4iSfQHw5S8YFMVPMiWSJ3s-RY2V5VWBt1njWAjq~4fzDwSVXp6h0a5rPmG0oYa6~BId~sz8Blab2PCPkUMeYkLRG5EsnQyDHwPdHgIVAgZb5fwyVuKldmyQnbqeeHj9DmyoP8AmUsH6OJ63GQw2GkjRhw0x9B3jfn079LdqH0fVX80-UIfv7FRT-gt9fINsBT3ujy5~GoP91mJwPh6EV9xbQmZvAI4j0~5adP4mD01zYum3SARXqPdtax9MWiC42Ad-ADQQje3hsuimaDlhIgELfFhRG1il3FEcNCdw4aA__&Key-Pair-Id=APKAIIBA3IODTCVA4BKQ",
  "url_expires_at": "2021-10-27T21:26:40.407880449Z",
  "created_at": "2021-10-27T14:20:31Z"
}
```

{% endtab %}
{% endtabs %}

### Uploading Player Files

#### Upload a Private Player File From Path

Upload a (private) player file from the file's path.

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

```csharp
LootLockerSDKManager.UploadPlayerFile("/path/to/file/save_game.zip", "save_game", response =>
{
    if (response.success)
    {
        Debug.Log("Successfully uploaded player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error uploading player file");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/qJICjwqRM4FNJKitxaS6" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/-sx-s5by/">Blueprint example of uploading a player file</a></p></figcaption></figure>

{% hint style="warning" %}
The Boolean "Is Public" should be set to false in this case, to upload a private file!
{% endhint %}
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/player/files" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01" \
  -F 'file=@"path/to/your/file"' \
  -F 'purpose="save_game"' \
  -F 'is_public="false"'
```

Example response:

```json
{
  "id": 318,
  "revision_id": "01GPNZHCVEF6F0QGA3V2B7CN44",
  "name": "save_game_1.zip",
  "size": 7056,
  "purpose": "SAVE_GAME_SLOT_1",
  "public": false,
  "url": "https://cdn.lootlocker.io/2/892/oJnNPGsiuzytMOJPatwtPilfsfykSBGp.jpg?Expires=1635366031&Signature=Z7MG~WgUyqpBNlXRePXxuV2uFbI0fEkAa3chk-3YO2XlWyd1veSl5V2dmFyVM1CndY6cfHo9ds3ilthwY~NjUiFmrx6ycfG3GJ5Z~kqzuyyMpH~LXTh~tIwnZpJpKScFUeBz5PpkSQPfOo4nuWktQBmpIgOhLlD2cWKOlQllBaIhjFvUh1HGIs-1u2-DiX9eDIgBDYzP4k0aMGH0aKjKF8Wb1jbAwaAcvQBeiPC~B~DgjlsHy6UjS59nYLCI-3EMheivk7H5-z-R65Au8VGg1koP89QtEjViy2HKbMKId~tQ3-cJg-ylRGHbE7jGlRyFC9CQLfeJXqx-IgaKfjVZCQ__&Key-Pair-Id=APKAIIBA3IODTCVA4BKQ",
  "url_expires_at": "2021-10-27T20:20:31.433987069Z",
  "created_at": "2021-10-27T14:20:31.428496822Z"
}
```

{% endtab %}
{% endtabs %}

#### Upload a Public Player File From Path

Upload a (public) player file from the file's path.

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

```csharp
// You can also set this to false, if omitted it defaults to false
bool isPublic = true;

LootLockerSDKManager.UploadPlayerFile("/path/to/file/save_game.zip", "save_game", isPublic, response =>
{
    if (response.success)
    {
        Debug.Log("Successfully uploaded player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error uploading player file");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/qJICjwqRM4FNJKitxaS6" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/-sx-s5by/">Blueprint example of uploading a player file</a></p></figcaption></figure>

{% hint style="warning" %}
The Boolean "Is Public" should be set to true in this case, to upload a public file!
{% endhint %}
{% endtab %}
{% endtabs %}

#### Upload a Private Player File Using FileStream

Upload a (private) file using FileStream.

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

```csharp
var file = File.Open("/path/to/file/save_game.zip"", FileMode.Open);
        
LootLockerSDKManager.UploadPlayerFile(file, "player_file", response =>
{
    if (response.success)
    {
        Debug.Log("Successfully uploaded player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error uploading player file");
    }
});
```

{% endtab %}
{% endtabs %}

#### Upload a Public Player File Using FileStream

Upload a (public) file using FileStream.

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

```csharp
var file = File.Open("/path/to/file/save_game.zip"", FileMode.Open);

// You can also set this to false, if omitted it defaults to false
bool isPublic = true;

LootLockerSDKManager.UploadPlayerFile(file, "player_file", isPublic, response =>
{
    if (response.success)
    {
        Debug.Log("Successfully uploaded player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error uploading player file");
    }
});
```

{% endtab %}
{% endtabs %}

#### Upload a Private Player File Using Byte Array

Upload a (private) file using byte array.

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

```csharp
var file = File.Open("/path/to/file/save_game.zip"", FileMode.Open);
var fileBytes = new byte[file.Length];
file.Read(fileBytes, 0, Convert.ToInt32(file.Length));

LootLockerSDKManager.UploadPlayerFile(fileBytes, "filename", "save_game", response =>
{
    if (response.success)
    {
        Debug.Log("Successfully uploaded player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error uploading player file");
    }
});
```

{% endtab %}
{% endtabs %}

#### Upload a Public Player File Using Byte Array

Upload a (public) file using byte array.

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

```csharp
var file = File.Open("/path/to/file/save_game.zip"", FileMode.Open);
var fileBytes = new byte[file.Length];
file.Read(fileBytes, 0, Convert.ToInt32(file.Length));

// You can also set this to false, if omitted it defaults to false
bool isPublic = true;

LootLockerSDKManager.UploadPlayerFile(fileBytes, "filename", "save_game", isPublic, response =>
{
    if (response.success)
    {
        Debug.Log("Successfully uploaded player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error uploading player file");
    }
});
```

{% endtab %}
{% endtabs %}

### Updating Player Files

#### Update a Player File from Path

Update a player file from the file's path.

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

```csharp
// The ID of the file can be retrieved when creating the file
// or when listing all player files.
int playerFileID = 0;
LootLockerSDKManager.UpdatePlayerFile(playerFileID, "/path/to/file/save_game.zip", response =>
{
    if (response.success)
    {
        Debug.Log("Successfully updated player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error updating player file");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/NcTJnSnvofOyqZPZpfwI" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/cqxeq04h/">Blueprint example of updating a player file</a></p></figcaption></figure>

{% hint style="danger" %}
Be aware that if you update a file, make sure to include everything, even what you don't want to update, as you can update a file to contain nothing.
{% endhint %}
{% endtab %}

{% tab title="REST" %}

```bash
curl -X PUT 'https://api.lootlocker.io/game/player/files/:file_id' \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01"
  -F 'file=@"path/to/your/file"'
```

Example response:

```json
{
  "id": 318,
  "revision_id": "01GPNZHCVEF6F0QGA3V2B7CN44",
  "name": "save_game_1.zip",
  "size": 7056,
  "purpose": "SAVE_GAME",
  "public": false,
  "url": "https://cdn.lootlocker.io/2/892/oJnNPGsiuzytMOJPatwtPilfsfykSBGp.jpg?Expires=1635366031&Signature=Z7MG~WgUyqpBNlXRePXxuV2uFbI0fEkAa3chk-3YO2XlWyd1veSl5V2dmFyVM1CndY6cfHo9ds3ilthwY~NjUiFmrx6ycfG3GJ5Z~kqzuyyMpH~LXTh~tIwnZpJpKScFUeBz5PpkSQPfOo4nuWktQBmpIgOhLlD2cWKOlQllBaIhjFvUh1HGIs-1u2-DiX9eDIgBDYzP4k0aMGH0aKjKF8Wb1jbAwaAcvQBeiPC~B~DgjlsHy6UjS59nYLCI-3EMheivk7H5-z-R65Au8VGg1koP89QtEjViy2HKbMKId~tQ3-cJg-ylRGHbE7jGlRyFC9CQLfeJXqx-IgaKfjVZCQ__&Key-Pair-Id=APKAIIBA3IODTCVA4BKQ",
  "url_expires_at": "2021-10-27T20:20:31.433987069Z",
  "created_at": "2021-10-27T14:20:31.428496822Z"
}
```

{% endtab %}
{% endtabs %}

#### Update a Player File Using Byte Array

Update a player file using byte array.

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

```csharp
// The ID of the file can be retrieved when creating the file
// or when listing all player files.
int playerFileID = 0;
var file = File.Open("/path/to/file/save_game.zip"", FileMode.Open);
var fileBytes = new byte[file.Length];
file.Read(fileBytes, 0, Convert.ToInt32(file.Length));
LootLockerSDKManager.UpdatePlayerFile(playerFileID, fileBytes, response =>
{
    if (response.success)
    {
        Debug.Log("Successfully updated player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error updating player file");
    }
});
```

{% endtab %}
{% endtabs %}

#### Update a Player File Using FileStream

Update a player file using FileStream.

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

```csharp
// The ID of the file can be retrieved when creating the file
// or when listing all player files.
int playerFileID = 0;
var file = File.Open("/path/to/file/save_game.zip"", FileMode.Open);
LootLockerSDKManager.UpdatePlayerFile(playerFileID, file, response =>
{
    if (response.success)
    {
        Debug.Log("Successfully updated player file, url: " + response.url);
    } 
    else
    {
        Debug.Log("Error updating player file");
    }
});
```

{% endtab %}
{% endtabs %}

### Deleting Player Files

#### Delete Player File

Delete a player file.

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

```csharp
// The ID of the file can be retrieved when creating the file
// or when listing all player files.
int playerFileId = 1;

LootLockerSDKManager.DeletePlayerFile(playerFileId, response => 
{
    if (response.success)
    {
        Debug.Log("Successfully deleted player file with id: " + playerFileId);
    } 
    else
    {
        Debug.Log("Error deleting player file");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/TBl8bn1Ln7COoBxxPoEE" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/t8zjut80/">Blueprint example of deleting a player file</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X DELETE "https://api.lootlocker.io/game/player/files/{file_id}" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01"
```

{% endtab %}
{% endtabs %}


# Friends & Followers

LootLocker supports a player-managed, cross-platform list of Friends and Followers. Players can add each other as Friends regardless of platform by sharing supported identifiers and having the other player accept the Friend request. Players can also choose to Follow other players, which does not require approval. In addition, players can block other players. Friends and Followers are independent systems, meaning you can use Friends without requiring Followers, and vice versa.

#### Types of Identifiers

Players can be found and connected using any of these provided identifiers (depending on your game’s auth setup):

* **Player ID:** Internal numeric identifier
* **Player ULID:** Global unique identifier (recommended for admin/console tasks)
* **Public UID:** Public, shareable identifier you can show to players
* **Player Name:** If enabled and unique
* **Guest Login ID:** For accounts created through Guest Login
* **Steam ID / Xbox ID / PSN ID:** First-party network IDs when linked

More information about Player Identifiers can be found in the [Glossary](https://docs.lootlocker.com/the-basics/core-concepts/glossary#lootlocker-player-identifiers).

### Use Cases

* Cross-platform friends list that travels with the player across PC/console/mobile
* Leaderboards only displaying scores from the player's friends
* Followers feed (e.g., follow streamers, clan leaders, or top players)
* Safety tools (blocking to avoid harassment and mute visibility)
* Live-ops hooks (target messages or rewards to friends/followers)

### How-To

* [Manage Relationships in Web Console](/players/friends-and-followers/how-to/manage-relationships-web-console)
* [Use Friends In-Game](/players/friends-and-followers/how-to/use-friends-in-game)
* [Use Followers In-Game](/players/friends-and-followers/how-to/use-followers-in-game)


# How To


# Manage Relationships in Web Console

In this how-to guide, we will manage a player’s relationships with other players. This guide demonstrates how to manually add and remove a player’s Friends and Followers, and how to add players to their Blocked list.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [A created game in the web console](https://docs.lootlocker.com/)
* Multiple players profiles (either by starting separate players sessions in-game or using different or by selecting Create Player in the [Player Manager](https://console.lootlocker.com/players))

### Manage Player Relationships

To manage a player’s relationships, first navigate to the Player Manager in the [Web Console](https://console.lootlocker.com/players). Search for a player or select one from the list of recent players.

![](/files/clW3hwReoWiORzwlcT25)

Once viewing a player’s profile, select the Relationships tab. In this view you can use the dropdown menu to choose the type of relationship to manage and the Add New Relationship button to add a player to that relationship type.

![](/files/WhMcbE82zJGXq1ZOYSXJ)

Below is a description of each relationship type.

* A **Friend** is a relationship that requires opt-in by both players. One player adds the other as a Friend, and the other player must accept the Friend request for them to become Friends. Until the request is accepted, the Friend status remains Pending and is viewable as Incoming or Outgoing in the Player Manager Relationships view.
* The **Blocked** relationship means that a player is unable to be Friends with or Follow the player who has blocked them.
* **Followers** lists players who are following the player in question.
* **Following** lists the players whom the player in question is following.

#### Add New Relationship

To manually add a new relationship, click Add New Relationship. Enter the ULID of the player you wish to add to the original player’s Friend, Blocked, Follower, or Following list.

Select the relationship type you want to add this player to, and click Submit to confirm.

![](/files/SJyHcgKEhGwhQpR9efO6)

**Locating Player ULID**

The Player ULID can be found in the Player Information section of the player’s profile.

![](/files/1a5Jg18Dto92oYz2UwMJ)

#### Delete an Existing Relationship

You can remove a player from another player’s relationship list by selecting the More Options button in the relationship list and choosing Delete Relationship. You can also select View Player to open that player’s profile in LootLocker.

![](/files/sFnjhiXdTw5Eu83T2HNU)

### Conclusion

In this How-to, we’ve managed a player's relationships by adding and removing friends and followers. Now that you are familar with these systems, it's time to implement usage of [friends](/players/friends-and-followers/how-to/use-friends-in-game) or [followers](/players/friends-and-followers/how-to/use-followers-in-game) in your game.


# Use Friend In-Game

In this How-to, we will implement a mutual Friends system in-game using LootLocker. You will learn how to send friend requests, accept or decline them, list your friends and pending requests, and handle blocking relationships. This creates opt-in, two-way connections between players.

## Prerequisites

* [A LootLocker account and a created game](https://lootlocker.com/sign-up)
* Multiple player profiles (e.g. create via separate sessions)
* [An active Game Session](/players/authentication)
* (Optional) Some player ULIDs collected from the [Player Manager](https://console.lootlocker.com/players)
* (Optional) Familiarity with [managing relationships in the Web Console](/players/friends-and-followers/how-to/manage-relationships-web-console)

## Friends vs Followers

Friends are a two-way relationship requiring mutual consent. Player A sends a friend request to Player B, and they only become friends once B accepts. This powers you to build:

* Private messaging systems
* Co-op gameplay invitations
* Trusted player groups for guilds or parties
* Mutual achievement sharing and challenges

If you need one-way relationships that don't require approval, use Followers instead (see: [Use Followers in Game](/players/friends-and-followers/how-to/use-followers-in-game)).

## Core Concepts

* **Friends list**: Players who have mutually accepted each other's friend requests.
* **Incoming requests**: Friend requests sent to the current player by others.
* **Outgoing requests**: Friend requests the current player has sent but not yet accepted/declined.
* **Blocked players**: Players who cannot send friend requests or interact with the current player.
* **Offset pagination**: Lists use page numbers and per-page counts to paginate the data.

## Typical UX Flow

1. Open Social UI → fetch friend counts for header
2. Browse friends list, incoming requests, or outgoing requests in tabs
3. Send friend request from player profile → appears in target's incoming list
4. Accept/decline incoming requests → moves to friends list or disappears
5. Cancel outgoing requests if changed mind
6. Block/unblock players to manage harassment

## List Friends and Requests

The foundation of any friends system is showing existing relationships and pending requests.

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

```csharp
// Listing Friends, Incoming/Outgoing Requests with Offset Pagination
// Unity Friends API uses Page (0-indexed) and PerPage to paginate the data.
// Assumptions:
// - UI layer methods: UIFriendsList.SetLoading(bool), UIFriendsList.ReplaceItems(array), etc.
// - Error handling via LogFailure(context, response)

using System.Collections.Generic;
using LootLocker.Requests;

public class FriendsListManager
{
    private readonly List<LootLockerAcceptedFriend> _friends = new();
    private readonly List<LootLockerFriend> _incomingRequests = new();
    private readonly List<LootLockerFriend> _outgoingRequests = new();
    private readonly List<LootLockerBlockedPlayer> _blockedPlayers = new();

    // Page tracking (0-indexed)
    private int _friendsCurrentPage = 0;
    private int _incomingCurrentPage = 0;
    private int _outgoingCurrentPage = 0;
    private int _blockedCurrentPage = 0;

    private const int PageSize = 20;

    // Friends List
    public void LoadFriendsPage()
    {
        _friends.Clear();
        _friendsCurrentPage = 0;
        UIFriendsList.SetLoading(true);
        
        LootLockerSDKManager.ListFriendsPaginated(PageSize, _friendsCurrentPage, (resp) =>
        {
            UIFriendsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Friends list", resp);
                return;
            }

            if (resp.friends != null)
            {
                _friends.AddRange(resp.friends);
                UIFriendsList.ReplaceItems(resp.friends);
            }

            bool hasMore = resp.friends != null && resp.pagination.total > _friends.Length;
            UIFriendsList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreFriends()
    {
        _friendsCurrentPage++;
        UIFriendsList.SetLoading(true);
        
        LootLockerSDKManager.ListFriendsPaginated(PageSize, _friendsCurrentPage, (resp) =>
        {
            UIFriendsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Friends list (more)", resp);
                _friendsCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.friends != null && resp.friends.Length > 0)
            {
                _friends.AddRange(resp.friends);
                UIFriendsList.AppendItems(resp.friends);
            }

            bool hasMore = resp.friends != null && resp.pagination.total > _friends.Length;
            UIFriendsList.SetHasMorePages(hasMore);
        });
    }

    // Incoming Friend Requests
    public void LoadIncomingRequestsFirstPage()
    {
        _incomingRequests.Clear();
        _incomingCurrentPage = 0;
        UIIncomingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListIncomingFriendRequestsPaginated(PageSize, _incomingCurrentPage, (resp) =>
        {
            UIIncomingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Incoming requests", resp);
                return;
            }

            if (resp.incoming != null)
            {
                _incomingRequests.AddRange(resp.incoming);
                UIIncomingRequestsList.ReplaceItems(resp.incoming);
            }

            bool hasMore = resp.incoming != null && resp.pagination.total > _incomingRequests.Length;
            UIIncomingRequestsList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreIncomingRequests()
    {
        _incomingCurrentPage++;
        UIIncomingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListIncomingFriendRequestsPaginated(PageSize, _incomingCurrentPage, (resp) =>
        {
            UIIncomingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Incoming requests (more)", resp);
                _incomingCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.incoming != null && resp.incoming.Length > 0)
            {
                _incomingRequests.AddRange(resp.incoming);
                UIIncomingRequestsList.AppendItems(resp.incoming);
            }

            bool hasMore = resp.incoming != null && resp.pagination.total > _incomingRequests.Length;
            UIIncomingRequestsList.SetHasMorePages(hasMore);
        });
    }

    // Outgoing Friend Requests
    public void LoadOutgoingRequestsFirstPage()
    {
        _outgoingRequests.Clear();
        _outgoingCurrentPage = 0;
        UIOutgoingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListOutGoingFriendRequestsPaginated(PageSize, _outgoingCurrentPage, (resp) =>
        {
            UIOutgoingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Outgoing requests", resp);
                return;
            }

            if (resp.outgoing != null)
            {
                _outgoingRequests.AddRange(resp.outgoing);
                UIOutgoingRequestsList.ReplaceItems(resp.outgoing);
            }

            bool hasMore = resp.outgoing != null && resp.pagination.total > _outgoingRequests.Length;
            UIOutgoingRequestsList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreOutgoingRequests()
    {
        _outgoingCurrentPage++;
        UIOutgoingRequestsList.SetLoading(true);
        
        LootLockerSDKManager.ListOutGoingFriendRequestsPaginated(PageSize, _outgoingCurrentPage, (resp) =>
        {
            UIOutgoingRequestsList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Outgoing requests (more)", resp);
                _outgoingCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.outgoing != null && resp.outgoing.Length > 0)
            {
                _outgoingRequests.AddRange(resp.outgoing);
                UIOutgoingRequestsList.AppendItems(resp.outgoing);
            }

            bool hasMore = resp.outgoing != null && resp.pagination.total > _outgoingRequests.Length;
            UIOutgoingRequestsList.SetHasMorePages(hasMore);
        });
    }

    // Blocked Players
    public void LoadBlockedPlayersFirstPage()
    {
        _blockedPlayers.Clear();
        _blockedCurrentPage = 0;
        UIBlockedPlayersList.SetLoading(true);
        
        LootLockerSDKManager.ListBlockedPlayersPaginated(PageSize, _blockedCurrentPage, (resp) =>
        {
            UIBlockedPlayersList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Blocked players", resp);
                return;
            }

            if (resp.blocked != null)
            {
                _blockedPlayers.AddRange(resp.blocked);
                UIBlockedPlayersList.ReplaceItems(resp.blocked);
            }

            bool hasMore = resp.blocked != null && resp.pagination.total > _blockedPlayers.Length;
            UIBlockedPlayersList.SetHasMorePages(hasMore);
        });
    }

    public void LoadMoreBlockedPlayers()
    {
        _blockedCurrentPage++;
        UIBlockedPlayersList.SetLoading(true);
        
        LootLockerSDKManager.ListBlockedPlayersPaginated(PageSize, _blockedCurrentPage, (resp) =>
        {
            UIBlockedPlayersList.SetLoading(false);
            if (!resp.success)
            {
                LogFailure("Blocked players (more)", resp);
                _blockedCurrentPage--; // Rollback page increment
                return;
            }

            if (resp.blocked != null && resp.blocked.Length > 0)
            {
                _blockedPlayers.AddRange(resp.blocked);
                UIBlockedPlayersList.AppendItems(resp.blocked);
            }

            bool hasMore = resp.blocked != null && resp.pagination.total > _blockedPlayers.Length;
            UIBlockedPlayersList.SetHasMorePages(hasMore);
        });
    }

    private void LogFailure(string context, LootLockerResponse resp)
    {
        UnityEngine.Debug.LogWarning($"[FriendsListManager] {context} failed: {resp?.errorData?.message}");
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friends List Management - Unreal C++
// Shows listing friends, incoming/outgoing requests, and blocked players using offset pagination.
// Assumptions:
// - UI layer methods for loading states and data display

#include "LootLockerManager.h"
#include "LootLockerSDK/LLFriends.h"

class FFriendsListManager
{
public:
    TArray<FLootLockerAcceptedFriend> Friends;
    TArray<FLootLockerFriend> IncomingRequests;
    TArray<FLootLockerFriend> OutgoingRequests;
    TArray<FLootLockerBlockedPlayer> BlockedPlayers;

    // Page tracking (0-indexed)
    int32 FriendsCurrentPage = 0;
    int32 IncomingCurrentPage = 0;
    int32 OutgoingCurrentPage = 0;
    int32 BlockedCurrentPage = 0;

    static constexpr int32 PageSize = 20;

    // Friends List
    void LoadFriendsFirstPage()
    {
        Friends.Empty();
        FriendsCurrentPage = 0;
        UFriendsListWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListFriendsPaginated(PageSize, FriendsCurrentPage,
            FLootLockerListFriendsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnFriendsLoaded));
    }

    void LoadMoreFriends()
    {
        FriendsCurrentPage++;
        UFriendsListWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListFriendsPaginated(PageSize, FriendsCurrentPage,
            FLootLockerListFriendsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreFriendsLoaded));
    }

    // Incoming Requests
    void LoadIncomingRequestsFirstPage()
    {
        IncomingRequests.Empty();
        IncomingCurrentPage = 0;
        UIncomingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListIncomingFriendRequestsPaginated(PageSize, IncomingCurrentPage,
            FLootLockerListIncomingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnIncomingRequestsLoaded));
    }

    void LoadMoreIncomingRequests()
    {
        IncomingCurrentPage++;
        UIncomingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListIncomingFriendRequestsPaginated(PageSize, IncomingCurrentPage,
            FLootLockerListIncomingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreIncomingRequestsLoaded));
    }

    // Outgoing Requests
    void LoadOutgoingRequestsFirstPage()
    {
        OutgoingRequests.Empty();
        OutgoingCurrentPage = 0;
        UOutgoingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListOutgoingFriendRequestsPaginated(PageSize, OutgoingCurrentPage,
            FLootLockerListOutgoingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnOutgoingRequestsLoaded));
    }

    void LoadMoreOutgoingRequests()
    {
        OutgoingCurrentPage++;
        UOutgoingRequestsWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListOutgoingFriendRequestsPaginated(PageSize, OutgoingCurrentPage,
            FLootLockerListOutgoingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreOutgoingRequestsLoaded));
    }

    // Blocked Players
    void LoadBlockedPlayersFirstPage()
    {
        BlockedPlayers.Empty();
        BlockedCurrentPage = 0;
        UBlockedPlayersWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListBlockedPlayersPaginated(PageSize, BlockedCurrentPage,
            FLootLockerListBlockedPlayersResponseDelegate::CreateRaw(this, &FFriendsListManager::OnBlockedPlayersLoaded));
    }

    void LoadMoreBlockedPlayers()
    {
        BlockedCurrentPage++;
        UBlockedPlayersWidget::SetLoading(true);
        
        ULootLockerSDKManager::ListBlockedPlayersPaginated(PageSize, BlockedCurrentPage,
            FLootLockerListBlockedPlayersResponseDelegate::CreateRaw(this, &FFriendsListManager::OnMoreBlockedPlayersLoaded));
    }

private:
    void OnFriendsLoaded(const FLootLockerListFriendsResponse& Response)
    {
        UFriendsListWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Friends list failed: %s"), *Response.Error);
            return;
        }

        Friends = Response.Friends;
        UFriendsListWidget::ReplaceItems(Friends);
        
        bool bHasMore = Friends.Num < Response.Pagination.Total;
        UFriendsListWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreFriendsLoaded(const FLootLockerListFriendsResponse& Response)
    {
        UFriendsListWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More friends failed: %s"), *Response.Error);
            FriendsCurrentPage--; // Rollback
            return;
        }

        if (Response.Friends.Num() > 0)
        {
            Friends.Append(Response.Friends);
            UFriendsListWidget::AppendItems(Response.Friends);
        }

        bool bHasMore = Friends.Num < Response.Pagination.Total;
        UFriendsListWidget::SetHasMorePages(bHasMore);
    }

    void OnIncomingRequestsLoaded(const FLootLockerListIncomingFriendRequestsResponse& Response)
    {
        UIncomingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Incoming requests failed: %s"), *Response.Error);
            return;
        }

        IncomingRequests = Response.Incoming;
        UIncomingRequestsWidget::ReplaceItems(IncomingRequests);
        
        bool bHasMore = IncomingRequests.Num() < Response.Pagination.Total;
        UIncomingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreIncomingRequestsLoaded(const FLootLockerListIncomingFriendRequestsResponse& Response)
    {
        UIncomingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More incoming requests failed: %s"), *Response.Error);
            IncomingCurrentPage--; // Rollback
            return;
        }

        if (Response.Incoming.Num() > 0)
        {
            IncomingRequests.Append(Response.Incoming);
            UIncomingRequestsWidget::AppendItems(Response.Incoming);
        }

        bool bHasMore = IncomingRequests.Num() < Response.Pagination.Total;
        UIncomingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnOutgoingRequestsLoaded(const FLootLockerListOutgoingFriendRequestsResponse& Response)
    {
        UOutgoingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Outgoing requests failed: %s"), *Response.Error);
            return;
        }

        OutgoingRequests = Response.Outgoing;
        UOutgoingRequestsWidget::ReplaceItems(OutgoingRequests);
        
        bool bHasMore = OutgoingRequests.Num() < Response.Pagination.Total;
        UOutgoingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreOutgoingRequestsLoaded(const FLootLockerListOutgoingFriendRequestsResponse& Response)
    {
        UOutgoingRequestsWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More outgoing requests failed: %s"), *Response.Error);
            OutgoingCurrentPage--; // Rollback
            return;
        }

        if (Response.Outgoing.Num() > 0)
        {
            OutgoingRequests.Append(Response.Outgoing);
            UOutgoingRequestsWidget::AppendItems(Response.Outgoing);
        }

        bool bHasMore = OutgoingRequests.Num() < Response.Pagination.Total;
        UOutgoingRequestsWidget::SetHasMorePages(bHasMore);
    }

    void OnBlockedPlayersLoaded(const FLootLockerListBlockedPlayersResponse& Response)
    {
        UBlockedPlayersWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("Blocked players failed: %s"), *Response.Error);
            return;
        }

        BlockedPlayers = Response.Blocked;
        UBlockedPlayersWidget::ReplaceItems(BlockedPlayers);
        
        bool bHasMore = BlockedPlayers.Num() < Response.Pagination.Total;
        UBlockedPlayersWidget::SetHasMorePages(bHasMore);
    }

    void OnMoreBlockedPlayersLoaded(const FLootLockerListBlockedPlayersResponse& Response)
    {
        UBlockedPlayersWidget::SetLoading(false);
        if (!Response.success)
        {
            UE_LOG(LogTemp, Warning, TEXT("More blocked players failed: %s"), *Response.Error);
            BlockedCurrentPage--; // Rollback
            return;
        }

        if (Response.Blocked.Num() > 0)
        {
            BlockedPlayers.Append(Response.Blocked);
            UBlockedPlayersWidget::AppendItems(Response.Blocked);
        }

        bool bHasMore = BlockedPlayers.Num() < Response.Pagination.Total;
        UBlockedPlayersWidget::SetHasMorePages(bHasMore);
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

### Offset Pagination Strategy

* Use page numbers (0-indexed) and `PerPage` count to paginate the data
* Track current page for each list type separately
* Consider a page "complete" if returned items equal the requested `PerPage`
* Reset to page 0 when refreshing lists

{% hint style="info" %}
Unlike cursor-based pagination, offset pagination lets you jump to specific pages without knowing the content on them
{% endhint %}

## Send and Cancel Friend Requests

Allow players to initiate and manage outgoing friend requests.

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

```csharp
// Send and Cancel Friend Requests with Optimistic UI
// Assumptions:
// - _outgoingRequests list is maintained locally
// - UI shows button states: Send Request / Pending / Friends
// - ShowToast for user feedback

public class FriendRequestController
{
    private readonly List<LootLockerFriend> _outgoingRequests;
    private readonly HashSet<string> _friendIds; // Track current friends for button state

    public FriendRequestController(List<LootLockerFriend> outgoingRequests, HashSet<string> friendIds)
    {
        _outgoingRequests = outgoingRequests;
        _friendIds = friendIds;
    }

    public void SendFriendRequest(string targetPlayerULID, string displayName)
    {
        if (string.IsNullOrEmpty(targetPlayerULID)) return;
        
        // Check if already friends or request pending
        if (_friendIds.Contains(targetPlayerULID))
        {
            ShowToast("Already friends");
            return;
        }
        
        if (_outgoingRequests.Any(r => r.player_ulid == targetPlayerULID))
        {
            ShowToast("Request already sent");
            return;
        }

        // Optimistic update
        var tempRequest = new LootLockerFriend 
        { 
            player_ulid = targetPlayerULID, 
            player_name = displayName,
            created_at = DateTime.Now
        };
        _outgoingRequests.Add(tempRequest);
        UIProfileButton.SetState(FriendButtonState.Pending);
        UIOutgoingRequestsList.AddItem(tempRequest);

        LootLockerSDKManager.SendFriendRequest(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback optimistic update
                _outgoingRequests.RemoveAll(r => r.player_ulid == targetPlayerULID);
                UIProfileButton.SetState(FriendButtonState.SendRequest);
                UIOutgoingRequestsList.RemoveItem(targetPlayerULID);
                ShowToast($"Failed to send request: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Friend request sent to {displayName}");
        });
    }

    public void CancelFriendRequest(string targetPlayerULID, string displayName)
    {
        var request = _outgoingRequests.FirstOrDefault(r => r.player_ulid == targetPlayerULID);
        if (request == null) return;

        // Optimistic removal
        _outgoingRequests.Remove(request);
        UIProfileButton.SetState(FriendButtonState.SendRequest);
        UIOutgoingRequestsList.RemoveItem(targetPlayerULID);

        LootLockerSDKManager.CancelFriendRequest(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _outgoingRequests.Add(request);
                UIProfileButton.SetState(FriendButtonState.Pending);
                UIOutgoingRequestsList.AddItem(request);
                ShowToast($"Failed to cancel request: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Cancelled request to {displayName}");
        });
    }

    private void ShowToast(string message)
    {
        // Implementation depends on your UI framework
        Debug.Log(message);
    }
}

public enum FriendButtonState
{
    SendRequest,
    Pending,
    Friends
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friend Request Management - Unreal C++
// Handles sending and canceling friend requests with optimistic updates

class FFriendRequestController
{
public:
    TArray<FLootLockerFriend> OutgoingRequests;
    TSet<FString> FriendIds; // Track current friends for UI state

    void SendFriendRequest(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        if (TargetPlayerULID.IsEmpty()) return;
        
        if (FriendIds.Contains(TargetPlayerULID))
        {
            ShowToast(TEXT("Already friends"));
            return;
        }
        
        // Check if request already exists
        if (OutgoingRequests.ContainsByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == TargetPlayerULID;
        }))
        {
            ShowToast(TEXT("Request already sent"));
            return;
        }

        // Optimistic update
        FLootLockerFriend TempRequest;
        TempRequest.Player_ulid = TargetPlayerULID;
        TempRequest.Player_name = DisplayName;
        TempRequest.Created_at = FDateTime::Now().ToString();
        
        OutgoingRequests.Add(TempRequest);
        UProfileButtonWidget::SetState(EFriendButtonState::Pending);
        UOutgoingRequestsWidget::AddItem(TempRequest);

        ULootLockerSDKManager::SendFriendRequest(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, TargetPlayerULID, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                OutgoingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
                    return Request.Player_ulid == TargetPlayerULID;
                });
                UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
                UOutgoingRequestsWidget::RemoveItem(TargetPlayerULID);
                ShowToast(FString::Printf(TEXT("Failed to send request: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Friend request sent to %s"), *DisplayName));
        }));
    }

    void CancelFriendRequest(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        // Find the request
        FLootLockerFriend* RequestPtr = OutgoingRequests.FindByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == TargetPlayerULID;
        });
        
        if (!RequestPtr) return;
        FLootLockerFriend RequestCopy = *RequestPtr;

        // Optimistic removal
        OutgoingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == TargetPlayerULID;
        });
        UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
        UOutgoingRequestsWidget::RemoveItem(TargetPlayerULID);

        ULootLockerSDKManager::CancelFriendRequest(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, RequestCopy, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                OutgoingRequests.Add(RequestCopy);
                UProfileButtonWidget::SetState(EFriendButtonState::Pending);
                UOutgoingRequestsWidget::AddItem(RequestCopy);
                ShowToast(FString::Printf(TEXT("Failed to cancel request: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Cancelled request to %s"), *DisplayName));
        }));
    }

private:
    void ShowToast(const FString& Message)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
        // Add your toast UI implementation
    }
};

UENUM(BlueprintType)
enum class EFriendButtonState : uint8
{
    SendRequest,
    Pending,
    Friends
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Accept and Decline Friend Requests

Handle incoming friend requests to build your friends network.

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

```csharp
// Accept and Decline Incoming Friend Requests
// Move requests between lists when processed

public class IncomingRequestsController
{
    private readonly List<LootLockerFriend> _incomingRequests;
    private readonly List<LootLockerAcceptedFriend> _friends;

    public IncomingRequestsController(List<LootLockerFriend> incomingRequests, List<LootLockerAcceptedFriend> friends)
    {
        _incomingRequests = incomingRequests;
        _friends = friends;
    }

    public void AcceptFriendRequest(string fromPlayerULID)
    {
        var request = _incomingRequests.FirstOrDefault(r => r.player_ulid == fromPlayerULID);
        if (request == null) return;

        // Optimistic update: move from incoming to friends
        _incomingRequests.Remove(request);
        var newFriend = new LootLockerAcceptedFriend
        {
            player_ulid = request.player_ulid,
            player_name = request.player_name,
            player_id = request.player_id,
            created_at = request.created_at,
            accepted_at = DateTime.Now
        };
        _friends.Add(newFriend);

        UIIncomingRequestsList.RemoveItem(fromPlayerULID);
        UIFriendsList.AddItem(newFriend);
        ShowToast($"Now friends with {request.player_name}");

        LootLockerSDKManager.AcceptFriendRequest(fromPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback: move back to incoming
                _friends.Remove(newFriend);
                _incomingRequests.Add(request);
                UIFriendsList.RemoveItem(fromPlayerULID);
                UIIncomingRequestsList.AddItem(request);
                ShowToast($"Failed to accept request: {resp.errorData?.message}");
            }
        });
    }

    public void DeclineFriendRequest(string fromPlayerULID)
    {
        var request = _incomingRequests.FirstOrDefault(r => r.player_ulid == fromPlayerULID);
        if (request == null) return;

        // Optimistic removal
        _incomingRequests.Remove(request);
        UIIncomingRequestsList.RemoveItem(fromPlayerULID);
        ShowToast($"Declined request from {request.player_name}");

        LootLockerSDKManager.DeclineFriendRequest(fromPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _incomingRequests.Add(request);
                UIIncomingRequestsList.AddItem(request);
                ShowToast($"Failed to decline request: {resp.errorData?.message}");
            }
        });
    }

    private void ShowToast(string message)
    {
        Debug.Log(message);
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Accept and Decline Friend Requests - Unreal C++

class FIncomingRequestsController
{
public:
    TArray<FLootLockerFriend> IncomingRequests;
    TArray<FLootLockerAcceptedFriend> Friends;

    void AcceptFriendRequest(const FString& FromPlayerULID)
    {
        // Find the request
        FLootLockerFriend* RequestPtr = IncomingRequests.FindByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });
        
        if (!RequestPtr) return;
        FLootLockerFriend RequestCopy = *RequestPtr;

        // Optimistic update: move from incoming to friends
        IncomingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });

        FLootLockerAcceptedFriend NewFriend;
        NewFriend.Player_ulid = RequestCopy.Player_ulid;
        NewFriend.Player_name = RequestCopy.Player_name;
        NewFriend.Player_id = RequestCopy.Player_id;
        NewFriend.Created_at = RequestCopy.Created_at;
        NewFriend.Accepted_at = FDateTime::Now().ToString();
        
        Friends.Add(NewFriend);
        UIncomingRequestsWidget::RemoveItem(FromPlayerULID);
        UFriendsListWidget::AddItem(NewFriend);
        ShowToast(FString::Printf(TEXT("Now friends with %s"), *RequestCopy.Player_name));

        ULootLockerSDKManager::AcceptFriendRequest(FromPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, RequestCopy, NewFriend, FromPlayerULID](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback: move back to incoming
                Friends.RemoveAll([&](const FLootLockerAcceptedFriend& Friend) {
                    return Friend.Player_ulid == FromPlayerULID;
                });
                IncomingRequests.Add(RequestCopy);
                UFriendsListWidget::RemoveItem(FromPlayerULID);
                UIncomingRequestsWidget::AddItem(RequestCopy);
                ShowToast(FString::Printf(TEXT("Failed to accept request: %s"), *Response.Error));
            }
        }));
    }

    void DeclineFriendRequest(const FString& FromPlayerULID)
    {
        // Find the request
        FLootLockerFriend* RequestPtr = IncomingRequests.FindByPredicate([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });
        
        if (!RequestPtr) return;
        FLootLockerFriend RequestCopy = *RequestPtr;

        // Optimistic removal
        IncomingRequests.RemoveAll([&](const FLootLockerFriend& Request) {
            return Request.Player_ulid == FromPlayerULID;
        });
        UIncomingRequestsWidget::RemoveItem(FromPlayerULID);
        ShowToast(FString::Printf(TEXT("Declined request from %s"), *RequestCopy.Player_name));

        ULootLockerSDKManager::DeclineFriendRequest(FromPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, RequestCopy](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                IncomingRequests.Add(RequestCopy);
                UIncomingRequestsWidget::AddItem(RequestCopy);
                ShowToast(FString::Printf(TEXT("Failed to decline request: %s"), *Response.Error));
            }
        }));
    }

private:
    void ShowToast(const FString& Message)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Block and Unblock Players

Manage your blocked players list to prevent unwanted interactions.

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

```csharp
// Block and Unblock Players
// Blocking removes from friends and prevents future friend requests

public class BlockingController
{
    private readonly List<LootLockerBlockedPlayer> _blockedPlayers;
    private readonly List<LootLockerAcceptedFriend> _friends;

    public BlockingController(List<LootLockerBlockedPlayer> blockedPlayers, List<LootLockerAcceptedFriend> friends)
    {
        _blockedPlayers = blockedPlayers;
        _friends = friends;
    }

    public void BlockPlayer(string targetPlayerULID, string displayName)
    {
        if (string.IsNullOrEmpty(targetPlayerULID)) return;

        // Check if already blocked
        if (_blockedPlayers.Any(p => p.player_ulid == targetPlayerULID))
        {
            ShowToast("Player already blocked");
            return;
        }

        // Optimistic update: remove from friends if present, add to blocked
        var existingFriend = _friends.FirstOrDefault(f => f.player_ulid == targetPlayerULID);
        if (existingFriend != null)
        {
            _friends.Remove(existingFriend);
            UIFriendsList.RemoveItem(targetPlayerULID);
        }

        var blockedPlayer = new LootLockerBlockedPlayer
        {
            player_ulid = targetPlayerULID,
            player_name = displayName,
            blocked_at = DateTime.Now
        };
        _blockedPlayers.Add(blockedPlayer);
        UIBlockedPlayersList.AddItem(blockedPlayer);
        UIProfileButton.SetState(FriendButtonState.Blocked);

        LootLockerSDKManager.BlockPlayer(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _blockedPlayers.Remove(blockedPlayer);
                UIBlockedPlayersList.RemoveItem(targetPlayerULID);
                if (existingFriend != null)
                {
                    _friends.Add(existingFriend);
                    UIFriendsList.AddItem(existingFriend);
                    UIProfileButton.SetState(FriendButtonState.Friends);
                }
                else
                {
                    UIProfileButton.SetState(FriendButtonState.SendRequest);
                }
                ShowToast($"Failed to block player: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Blocked {displayName}");
        });
    }

    public void UnblockPlayer(string targetPlayerULID, string displayName)
    {
        var blockedPlayer = _blockedPlayers.FirstOrDefault(p => p.player_ulid == targetPlayerULID);
        if (blockedPlayer == null) return;

        // Optimistic removal
        _blockedPlayers.Remove(blockedPlayer);
        UIBlockedPlayersList.RemoveItem(targetPlayerULID);
        UIProfileButton.SetState(FriendButtonState.SendRequest);

        LootLockerSDKManager.UnblockPlayer(targetPlayerULID, (resp) =>
        {
            if (!resp.success)
            {
                // Rollback
                _blockedPlayers.Add(blockedPlayer);
                UIBlockedPlayersList.AddItem(blockedPlayer);
                UIProfileButton.SetState(FriendButtonState.Blocked);
                ShowToast($"Failed to unblock player: {resp.errorData?.message}");
                return;
            }

            ShowToast($"Unblocked {displayName}");
        });
    }

    private void ShowToast(string message)
    {
        Debug.Log(message);
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Block and Unblock Players - Unreal C++

class FBlockingController
{
public:
    TArray<FLootLockerBlockedPlayer> BlockedPlayers;
    TArray<FLootLockerAcceptedFriend> Friends;

    void BlockPlayer(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        if (TargetPlayerULID.IsEmpty()) return;

        // Check if already blocked
        if (BlockedPlayers.ContainsByPredicate([&](const FLootLockerBlockedPlayer& Player) {
            return Player.Player_ulid == TargetPlayerULID;
        }))
        {
            ShowToast(TEXT("Player already blocked"));
            return;
        }

        // Remove from friends if present
        FLootLockerAcceptedFriend* ExistingFriend = Friends.FindByPredicate([&](const FLootLockerAcceptedFriend& Friend) {
            return Friend.Player_ulid == TargetPlayerULID;
        });

        FLootLockerAcceptedFriend FriendCopy;
        bool bWasFriend = false;
        if (ExistingFriend)
        {
            FriendCopy = *ExistingFriend;
            bWasFriend = true;
            Friends.RemoveAll([&](const FLootLockerAcceptedFriend& Friend) {
                return Friend.Player_ulid == TargetPlayerULID;
            });
            UFriendsListWidget::RemoveItem(TargetPlayerULID);
        }

        // Add to blocked
        FLootLockerBlockedPlayer BlockedPlayer;
        BlockedPlayer.Player_ulid = TargetPlayerULID;
        BlockedPlayer.Player_name = DisplayName;
        BlockedPlayer.Blocked_at = FDateTime::Now().ToString();
        
        BlockedPlayers.Add(BlockedPlayer);
        UBlockedPlayersWidget::AddItem(BlockedPlayer);
        UProfileButtonWidget::SetState(EFriendButtonState::Blocked);

        ULootLockerSDKManager::BlockPlayer(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, BlockedPlayer, FriendCopy, bWasFriend, TargetPlayerULID, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                BlockedPlayers.RemoveAll([&](const FLootLockerBlockedPlayer& Player) {
                    return Player.Player_ulid == TargetPlayerULID;
                });
                UBlockedPlayersWidget::RemoveItem(TargetPlayerULID);
                
                if (bWasFriend)
                {
                    Friends.Add(FriendCopy);
                    UFriendsListWidget::AddItem(FriendCopy);
                    UProfileButtonWidget::SetState(EFriendButtonState::Friends);
                }
                else
                {
                    UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
                }
                
                ShowToast(FString::Printf(TEXT("Failed to block player: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Blocked %s"), *DisplayName));
        }));
    }

    void UnblockPlayer(const FString& TargetPlayerULID, const FString& DisplayName)
    {
        FLootLockerBlockedPlayer* BlockedPlayerPtr = BlockedPlayers.FindByPredicate([&](const FLootLockerBlockedPlayer& Player) {
            return Player.Player_ulid == TargetPlayerULID;
        });
        
        if (!BlockedPlayerPtr) return;
        FLootLockerBlockedPlayer BlockedPlayerCopy = *BlockedPlayerPtr;

        // Optimistic removal
        BlockedPlayers.RemoveAll([&](const FLootLockerBlockedPlayer& Player) {
            return Player.Player_ulid == TargetPlayerULID;
        });
        UBlockedPlayersWidget::RemoveItem(TargetPlayerULID);
        UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);

        ULootLockerSDKManager::UnblockPlayer(TargetPlayerULID,
            FLootLockerFriendActionResponseDelegate::CreateLambda([this, BlockedPlayerCopy, DisplayName](const FLootLockerFriendActionResponse& Response)
        {
            if (!Response.success)
            {
                // Rollback
                BlockedPlayers.Add(BlockedPlayerCopy);
                UBlockedPlayersWidget::AddItem(BlockedPlayerCopy);
                UProfileButtonWidget::SetState(EFriendButtonState::Blocked);
                ShowToast(FString::Printf(TEXT("Failed to unblock player: %s"), *Response.Error));
                return;
            }

            ShowToast(FString::Printf(TEXT("Unblocked %s"), *DisplayName));
        }));
    }

private:
    void ShowToast(const FString& Message)
    {
        UE_LOG(LogTemp, Log, TEXT("%s"), *Message);
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Check Friendship Status

Determine the current relationship between players for UI state management.

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

```csharp
// Check Friendship Status Using GetFriend
// Returns specific friend data if they are friends, null/error otherwise

public static class FriendshipProbe
{
    public static void CheckFriendshipStatus(string targetPlayerULID, System.Action<FriendshipStatus> onResult)
    {
        if (string.IsNullOrEmpty(targetPlayerULID)) 
        { 
            onResult?.Invoke(FriendshipStatus.None); 
            return; 
        }

        LootLockerSDKManager.GetFriend(targetPlayerULID, (resp) =>
        {
            if (resp.success && !string.IsNullOrEmpty(resp.player_ulid))
            {
                // They are friends - check when friendship was established
                onResult?.Invoke(FriendshipStatus.Friends);
            }
            else
            {
                // Not friends - could be pending request, blocked, or no relationship
                // You might need additional checks to distinguish these states
                onResult?.Invoke(FriendshipStatus.None);
            }
        });
    }
}

public enum FriendshipStatus
{
    None,           // No relationship
    Friends,        // Mutual friends
    IncomingRequest, // They sent us a request
    OutgoingRequest, // We sent them a request
    Blocked         // One party blocked the other
}

// Usage:
// FriendshipProbe.CheckFriendshipStatus(otherPlayerUid, (status) => {
//     switch(status) {
//         case FriendshipStatus.Friends:
//             UIProfileButton.SetState(FriendButtonState.Friends);
//             break;
//         case FriendshipStatus.None:
//             UIProfileButton.SetState(FriendButtonState.SendRequest);
//             break;
//     }
// });
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friendship Status Check - Unreal C++

UENUM(BlueprintType)
enum class EFriendshipStatus : uint8
{
    None,           // No relationship
    Friends,        // Mutual friends  
    IncomingRequest, // They sent us a request
    OutgoingRequest, // We sent them a request
    Blocked         // One party blocked the other
};

class FFriendshipProbe
{
public:
    static void CheckFriendshipStatus(const FString& TargetPlayerULID, TFunction<void(EFriendshipStatus)> Callback)
    {
        if (TargetPlayerULID.IsEmpty()) 
        { 
            Callback(EFriendshipStatus::None); 
            return; 
        }

        ULootLockerSDKManager::GetFriend(TargetPlayerULID,
            FLootLockerGetFriendResponseDelegate::CreateLambda([Callback](const FLootLockerGetFriendResponse& Response)
        {
            if (Response.success && !Response.Player_ulid.IsEmpty())
            {
                // They are friends
                Callback(EFriendshipStatus::Friends);
            }
            else
            {
                // Not friends - could be pending, blocked, or no relationship
                // Additional logic could check pending requests lists
                Callback(EFriendshipStatus::None);
            }
        }));
    }
};

// Usage:
// FFriendshipProbe::CheckFriendshipStatus(OtherPlayerUid, [](EFriendshipStatus Status) {
//     switch(Status) {
//         case EFriendshipStatus::Friends:
//             UProfileButtonWidget::SetState(EFriendButtonState::Friends);
//             break;
//         case EFriendshipStatus::None:
//             UProfileButtonWidget::SetState(EFriendButtonState::SendRequest);
//             break;
//     }
// });
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Displaying Counts and Status

Show meaningful counts and relationship indicators in your UI.

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

```csharp
// Friends Counts and Status Display
// Use first page loads to estimate counts and provide status context

public class FriendsCountsManager
{
    private int _friendsCount = 0;
    private int _incomingRequestsCount = 0;
    private int _outgoingRequestsCount = 0;

    public void LoadAllCounts()
    {
        // Load first pages to get count estimates
        LoadFriendsCount();
        LoadIncomingRequestsCount();
        LoadOutgoingRequestsCount();
    }

    private void LoadFriendsCount()
    {
        LootLockerSDKManager.ListFriendsPaginated(1, 0, (resp) =>
        {
            if (resp.success)
            {
                UIFriendsHeader.SetCount(resp.pagination.total.ToString());
            }
        });
    }

    private void LoadIncomingRequestsCount()
    {
        LootLockerSDKManager.ListIncomingFriendRequestsPaginated(1, 0, (resp) =>
        {
            if (resp.success && resp.incoming != null)
            {
                _incomingRequestsCount = resp.pagination.total;
                UIIncomingRequestsHeader.SetCount(_incomingRequestsCount);
                
                // Show notification badge if there are pending requests
                if (_incomingRequestsCount > 0)
                {
                    UIFriendsTabButton.ShowBadge(_incomingRequestsCount);
                }
            }
        });
    }

    private void LoadOutgoingRequestsCount()
    {
        LootLockerSDKManager.ListOutGoingFriendRequestsPaginated(1, 0, (resp) =>
        {
            if (resp.success && resp.outgoing != null)
            {
                UIOutgoingRequestsHeader.SetCount(resp.pagination.total);
            }
        });
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Friends Counts Display - Unreal C++

class FFriendsCountsManager
{
public:
    int32 FriendsCount = 0;
    int32 IncomingRequestsCount = 0;
    int32 OutgoingRequestsCount = 0;

    void LoadAllCounts()
    {
        LoadFriendsCount();
        LoadIncomingRequestsCount();
        LoadOutgoingRequestsCount();
    }

private:
    void LoadFriendsCount()
    {
        ULootLockerSDKManager::ListFriendsPaginated(1, 0,
            FLootLockerListFriendsResponseDelegate::CreateRaw(this, &FFriendsCountsManager::OnFriendsCountLoaded));
    }

    void LoadIncomingRequestsCount()
    {
        ULootLockerSDKManager::ListIncomingFriendRequestsPaginated(1, 0,
            FLootLockerListIncomingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsCountsManager::OnIncomingCountLoaded));
    }

    void LoadOutgoingRequestsCount()
    {
        ULootLockerSDKManager::ListOutgoingFriendRequestsPaginated(1, 0,
            FLootLockerListOutgoingFriendRequestsResponseDelegate::CreateRaw(this, &FFriendsCountsManager::OnOutgoingCountLoaded));
    }

    void OnFriendsCountLoaded(const FLootLockerListFriendsResponse& Response)
    {
        if (Response.success)
        {
            UFriendsHeaderWidget::SetCount(FString::FromInt(Response.Pagination.Total));
        }
    }

    void OnIncomingCountLoaded(const FLootLockerListIncomingFriendRequestsResponse& Response)
    {
        if (Response.success)
        {
            IncomingRequestsCount = Response.Pagination.Total;
            UIncomingRequestsHeaderWidget::SetCount(FString::FromInt(IncomingRequestsCount));
            
            // Show notification badge if there are pending requests
            if (IncomingRequestsCount > 0)
            {
                UFriendsTabButtonWidget::ShowBadge(IncomingRequestsCount);
            }
        }
    }

    void OnOutgoingCountLoaded(const FLootLockerListOutgoingFriendRequestsResponse& Response)
    {
        if (Response.success)
        {
            OutgoingRequestsCount = Response.Pagination.Total;
            UOutgoingRequestsHeaderWidget::SetCount(FString::FromInt(OutgoingRequestsCount));
        }
    }
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Performance Tips

* **Batch relationship checks**: When showing many players (leaderboards for example), only fetch data for visible items
* **Cache friend IDs locally**: Maintain a set of friend ULIDs for instant button state rendering
* **Moderate page sizes**: 20-50 items per page balances responsiveness with network efficiency
* **Debounce rapid requests**: Prevent spam-clicking send/cancel buttons
* **Lazy load secondary lists**: Load friends first, then incoming/outgoing requests when tabs are opened

## Example Feature Ideas

* **Mutual Friends**: "You have 3 mutual friends with this player"
* **Friend Recommendations**: Suggest friends-of-friends or players with similar interests
* **Private Groups**: Create invite-only spaces using your friends list
* **Co-op Quick Join**: Join friends' game sessions directly from the friends list

## Conclusion

In this How-to we implemented friend requests, list management, acceptance/declining workflows, blocking functionality, and relationship status checking. The friends system provides the foundation for deeper social features like messaging, co-op play, and community building.

Key differences from followers include the mutual consent model, request management workflow, and offset-based pagination. Next, consider adding [Followers](/players/friends-and-followers/how-to/use-followers-in-game) for asymmetric relationships or explore [Web Console management](/players/friends-and-followers/how-to/manage-relationships-web-console) for administrative oversight.


# Use Followers In-Game

In this How-to, we will implement a basic Followers system in-game using LootLocker. You will learn how to show who follows a player, who they are following, allow players to follow/unfollow others, and build simple UI patterns like profile headers, lists, and follow buttons.

## Prerequisites

* [A LootLocker account and a created game](https://lootlocker.com/sign-up)
* Multiple player profiles (e.g. create via separate sessions)
* [An active Game Session](/players/authentication)
* (Optional) Some player ULIDs collected from the [Player Manager](https://console.lootlocker.com/players)
* (Optional) Familiarity with [managing relationships in the Web Console](/players/friends-and-followers/how-to/manage-relationships-web-console)

## Followers vs Friends

Followers are a one‑way relationship. Player A can follow Player B without approval (unless B has blocked A). This enables:

* Creator / influencer style player profiles
* Social feeds or “What your followed players did”
* Asynchronous competitions (e.g. “Chase the ghost of players you follow”)
* Notification or highlight surfaces (e.g. “New level published by someone you follow”)

If you need mutual, opt‑in relationships, use Friends instead (see: [Use Friends in Game](/players/friends-and-followers/how-to/use-friends-in-game)).

## Core Concepts

* Followers list: Players who follow the target player.
* Following list: Players the target player has chosen to follow.
* Pagination: Large lists return a `next_cursor`; request subsequent pages until empty.
* Blocking: A blocked player cannot follow or become friends with the blocker.
* UI Caching: Cache following IDs locally to render instant button states (“Following” vs “Follow”).

## Typical UX Flow

1. Open Profile (self or another player)
2. Fetch counts (followers + following) for header display
3. Lazy-load the tab the player opens first (e.g. “Following”)
4. Infinite scroll / “Load more” using cursor
5. Show Follow / Unfollow button with optimistic state change
6. Update local cache + optionally refresh counts

## Fetch Follower and Following Lists

Use these to populate tabs or modals.

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

```csharp
// This example shows how to:
// 1. Fetch the first page of followers for the logged-in player
// 2. Fetch the first page of players the logged-in player is following
// 3. Request additional pages using the `pagination.next_cursor`
//
// Assumptions / Pseudo Dependencies:
// - A Game Session has already been started (player authenticated)
// - You have a simple UI layer (not included here) with methods like:
//     UIFollowersList.SetLoading(bool)
//     UIFollowingList.AppendRows(IEnumerable<LootLockerFollower>)
//     UIFollowingList.ShowEndOfList()
// - You store cursors locally for each list.

using System.Collections.Generic;
using LootLocker.Requests; // Namespace where the SDK classes live

public class FollowersExample
{
	// Local caches (only store lightweight identity info; expand as needed)
	private readonly List<LootLockerFollower> _followers = new();
	private readonly List<LootLockerFollower> _following = new();

	// Pagination cursors (empty or null means no more pages / not yet loaded)
	private string _followersNextCursor = null;
	private string _followingNextCursor = null;

	// Page size – tune for your UX & bandwidth constraints
	private const int PageSize = 25;

	// PUBLIC API you might call from UI buttons / tab switches
	public void LoadFirstFollowersPage()
	{
		_followers.Clear();
		_followersNextCursor = null; // Reset so we know this is a fresh load
		UIFollowersList.SetLoading(true);

		// First page: pass null/empty cursor, and a count
		LootLockerSDKManager.ListFollowersPaginated(Cursor: null, Count: PageSize, onComplete: (resp) =>
		{
			UIFollowersList.SetLoading(false);
			if (!resp.success)
			{
				LogFailure("Followers", resp);
				return;
			}

			if (resp.followers != null)
			{
				_followers.AddRange(resp.followers);
				UIFollowersList.ReplaceRows(resp.followers); // Replace entire list for first load
			}

			_followersNextCursor = resp.pagination?.next_cursor; // Will be null/empty if no more pages
			if (string.IsNullOrEmpty(_followersNextCursor))
			{
				UIFollowersList.ShowEndOfList();
			}
		});
	}

	public void LoadMoreFollowers()
	{
		if (string.IsNullOrEmpty(_followersNextCursor)) return; // No more pages
		UIFollowersList.SetLoading(true);
		LootLockerSDKManager.ListFollowersPaginated(Cursor: _followersNextCursor, Count: PageSize, onComplete: (resp) =>
		{
			UIFollowersList.SetLoading(false);
			if (!resp.success)
			{
				LogFailure("Followers (more)", resp);
				return;
			}

			if (resp.followers != null && resp.followers.Length > 0)
			{
				_followers.AddRange(resp.followers);
				UIFollowersList.AppendRows(resp.followers); // Append for subsequent pages
			}

			_followersNextCursor = resp.pagination?.next_cursor;
			if (string.IsNullOrEmpty(_followersNextCursor))
			{
				UIFollowersList.ShowEndOfList();
			}
		});
	}

	public void LoadFirstFollowingPage()
	{
		_following.Clear();
		_followingNextCursor = null;
		UIFollowingList.SetLoading(true);
		LootLockerSDKManager.ListFollowingPaginated(Cursor: null, Count: PageSize, onComplete: (resp) =>
		{
			UIFollowingList.SetLoading(false);
			if (!resp.success)
			{
				LogFailure("Following", resp);
				return;
			}

			if (resp.following != null)
			{
				_following.AddRange(resp.following);
				UIFollowingList.ReplaceRows(resp.following);
			}

			_followingNextCursor = resp.pagination?.next_cursor;
			if (string.IsNullOrEmpty(_followingNextCursor))
			{
				UIFollowingList.ShowEndOfList();
			}
		});
	}

	public void LoadMoreFollowing()
	{
		if (string.IsNullOrEmpty(_followingNextCursor)) return;
		UIFollowingList.SetLoading(true);
		LootLockerSDKManager.ListFollowingPaginated(Cursor: _followingNextCursor, Count: PageSize, onComplete: (resp) =>
		{
			UIFollowingList.SetLoading(false);
			if (!resp.success)
			{
				LogFailure("Following (more)", resp);
				return;
			}

			if (resp.following != null && resp.following.Length > 0)
			{
				_following.AddRange(resp.following);
				UIFollowingList.AppendRows(resp.following);
			}

			_followingNextCursor = resp.pagination?.next_cursor;
			if (string.IsNullOrEmpty(_followingNextCursor))
			{
				UIFollowingList.ShowEndOfList();
			}
		});
	}

	private void LogFailure(string context, LootLockerResponse resp)
	{
		// Centralize error logging; you might map error codes to localized user messages.
		UnityEngine.Debug.LogWarning($"[FollowersExample] {context} failed: {resp?.errorData?.message}");
	}
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Example module-level (or UObject) snippet illustrating how to:
// 1. List first page of Followers and Following for the logged-in player
// 2. Request subsequent pages using the pagination cursor
// 3. Append results to in-memory arrays and notify a (hypothetical) UI layer
//
// Assumptions / Pseudo Dependencies:
// - You have already started a player session (authentication done).
// - FLootLockerListFollowersResponse contains: TArray<FLootLockerFollower> Followers and FLootLockerKeyBasedPagination Pagination.
// - UI layer functions (not provided):
//      UFollowersWidget::SetFollowersLoading(bool)
//      UFollowersWidget::ReplaceFollowers(const TArray<FLootLockerFollower>&)
//      UFollowersWidget::AppendFollowers(const TArray<FLootLockerFollower>&)
//      UFollowersWidget::ShowFollowersEndOfList()
//      UFollowingWidget::SetFollowingLoading(bool) ... etc.

#include "LootLockerManager.h"          // Pseudo include for where static API lives
#include "LootLockerSDK/LLFollowers.h"   // Pseudo header containing delegates & structs

class FFollowsExample
{
public:
	// Public arrays so your UI or ViewModel can read them (consider encapsulation in production)
	TArray<FLootLockerFollower> Followers;
	TArray<FLootLockerFollower> Following;

	// Stored cursors; empty string means: either not loaded yet (initial) or no more pages after last fetch
	FString FollowersNextCursor;
	FString FollowingNextCursor;

	// Tune to taste
	static constexpr int32 PageSize = 25;

	/* ---------------- Followers ---------------- */
	void LoadFirstFollowersPage()
	{
		Followers.Empty();
		FollowersNextCursor.Empty();
		UFollowersWidget::SetFollowersLoading(true);

		// Request first page (no cursor). Overload without explicit player UID uses current player.
		ULootLockerSDKManager::ListFollowers(
			FLootLockerListFollowersResponseDelegate::CreateRaw(this, &FFollowsExample::OnFollowersFirstPage));
	}

	void LoadMoreFollowers()
	{
		if (FollowersNextCursor.IsEmpty()) return; // Nothing more to load
		UFollowersWidget::SetFollowersLoading(true);

		ULootLockerSDKManager::ListFollowersPaginated(FollowersNextCursor, PageSize,
			FLootLockerListFollowersResponseDelegate::CreateRaw(this, &FFollowsExample::OnFollowersNextPage));
	}

private:
	void OnFollowersFirstPage(const FLootLockerListFollowersResponse& Response)
	{
		UFollowersWidget::SetFollowersLoading(false);
		if (!Response.success)
		{
			UE_LOG(LogTemp, Warning, TEXT("Followers first page failed: %s"), *Response.Error);
			return;
		}

		Followers = Response.Followers; // Copy first page
		UFollowersWidget::ReplaceFollowers(Followers);

		FollowersNextCursor = Response.Pagination.Next_Cursor; // Will be empty if no more pages
		if (FollowersNextCursor.IsEmpty())
		{
			UFollowersWidget::ShowFollowersEndOfList();
		}
	}

	void OnFollowersNextPage(const FLootLockerListFollowersResponse& Response)
	{
		UFollowersWidget::SetFollowersLoading(false);
		if (!Response.success)
		{
			UE_LOG(LogTemp, Warning, TEXT("Followers next page failed: %s"), *Response.Error);
			return;
		}

		if (Response.Followers.Num() > 0)
		{
			Followers.Append(Response.Followers);
			UFollowersWidget::AppendFollowers(Response.Followers);
		}

		FollowersNextCursor = Response.Pagination.Next_Cursor;
		if (FollowersNextCursor.IsEmpty())
		{
			UFollowersWidget::ShowFollowersEndOfList();
		}
	}

public:
	/* ---------------- Following ---------------- */
	void LoadFirstFollowingPage()
	{
		Following.Empty();
		FollowingNextCursor.Empty();
		UFollowingWidget::SetFollowingLoading(true);

		ULootLockerSDKManager::ListFollowing(
			FLootLockerListFollowersResponseDelegate::CreateRaw(this, &FFollowsExample::OnFollowingFirstPage));
	}

	void LoadMoreFollowing()
	{
		if (FollowingNextCursor.IsEmpty()) return;
		UFollowingWidget::SetFollowingLoading(true);
		ULootLockerSDKManager::ListFollowingPaginated(FollowingNextCursor, PageSize,
			FLootLockerListFollowersResponseDelegate::CreateRaw(this, &FFollowsExample::OnFollowingNextPage));
	}

private:
	void OnFollowingFirstPage(const FLootLockerListFollowersResponse& Response)
	{
		UFollowingWidget::SetFollowingLoading(false);
		if (!Response.success)
		{
			UE_LOG(LogTemp, Warning, TEXT("Following first page failed: %s"), *Response.Error);
			return;
		}

		Following = Response.Followers;
		UFollowingWidget::ReplaceFollowing(Following);
		FollowingNextCursor = Response.Pagination.Next_Cursor;
		if (FollowingNextCursor.IsEmpty())
		{
			UFollowingWidget::ShowFollowingEndOfList();
		}
	}

	void OnFollowingNextPage(const FLootLockerListFollowersResponse& Response)
	{
		UFollowingWidget::SetFollowingLoading(false);
		if (!Response.success)
		{
			UE_LOG(LogTemp, Warning, TEXT("Following next page failed: %s"), *Response.Error);
			return;
		}

		if (Response.Followers.Num() > 0)
		{
			Following.Append(Response.Followers);
			UFollowingWidget::AppendFollowing(Response.Followers);
		}

		FollowingNextCursor = Response.Pagination.Next_Cursor;
		if (FollowingNextCursor.IsEmpty())
		{
			UFollowingWidget::ShowFollowingEndOfList();
		}
	}
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

### Pagination Strategy

* Always request the first page with no cursor.
* Store `next_cursor` if present.
* Disable “Load more” when cursor is empty or null.
* Consider prefetching the next page when the user scrolls past 70% of current content.

{% hint style="info" %}
Show a skeleton or shimmer state while loading pages to keep the UI feeling responsive.
{% endhint %}

## Displaying Counts

Instead of loading entire lists to show numbers, use the first page response (which includes total/size fields) or lazily populate counts after the first fetch. If exact counts are not required immediately, show placeholders (e.g. “—” then fade in).

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

```csharp
// Displaying Counts Example
// There is no dedicated "counts only" endpoint, so we infer counts from:
// - Length of the page we just received
// - Presence (or absence) of pagination.next_cursor to know if more pages exist
// Optionally, you can lazily request the first page of each list only when a UI panel is opened.
//
// Assumptions:
// - UI has two text labels: UIFollowersHeader.SetCount(int? count, int? total) & UIFollowingHeader.SetCount(int? count, int? total)
// - Passing null shows a placeholder (e.g. "—") until data arrives.

public class FollowerCounts
{
	private int? _followersLoadedCount;
	private int? _followingLoadedCount;
	private int? _totalFollowersCount;
	private int? _totalFollowingCount;

	public void PrimePlaceholders()
	{
		UIFollowersHeader.SetCount(null, null);
		UIFollowingHeader.SetCount(null, null);
	}

	public void FetchInitialFollowers()
	{
		LootLockerSDKManager.ListFollowersPaginated(null, 25, (resp) =>
		{
			if (!resp.success)
			{
				// Handle error
				return;
			}
			_followersLoadedCount = resp.followers?.Length ?? 0;
			_totalFollowersCount = resp.pagination?.total);
			UIFollowersHeader.SetCount(_followersLoadedCount, _totalFollowersCount);
		});
	}

	public void FetchInitialFollowing()
	{
		LootLockerSDKManager.ListFollowingPaginated(null, 25, (resp) =>
		{
			if (!resp.success)
			{
				UIFollowingHeader.SetCount(0);
				return;
			}
			_followingLoadedCount = resp.following?.Length ?? 0;
			_totalFollowingCount = resp.pagination?.total);
			UIFollowingHeader.SetCount(_followingLoadedCount, _totalFollowingCount);
		});
	}
}
```

{% endtab %}

{% tab title="Unreal" %}

```cpp
// Displaying Counts (Unreal C++)
// Similar idea to Unity: infer partial counts from first page & presence of a next cursor.
// If you need an exact total across many pages, you would have to iterate until no cursor remains
// (often undesirable for large lists). Instead, present: "25+" when more pages exist.

class FFollowerCountsHelper
{
public:
	// Optional cached values; unset (-1) until fetched
	int32 FollowersFirstPageCount = -1;
	int32 FollowingFirstPageCount = -1;
	int32 FollowersFirstTotalCount = -1;
	int32 FollowingFirstTotalCount = -1;

	void FetchFollowersFirstPage()
	{
		ULootLockerSDKManager::ListFollowers(
			FLootLockerListFollowersResponseDelegate::CreateRaw(this, &FFollowerCountsHelper::OnFollowersPage));
	}

	void FetchFollowingFirstPage()
	{
		ULootLockerSDKManager::ListFollowing(
			FLootLockerListFollowersResponseDelegate::CreateRaw(this, &FFollowerCountsHelper::OnFollowingPage));
	}

private:
	void OnFollowersPage(const FLootLockerListFollowersResponse& Response)
	{
		if (!Response.success)
		{
			UE_LOG(LogTemp, Warning, TEXT("Followers count fetch failed: %s"), *Response.Error);
			FollowersFirstPageCount = 0; FollowersFirstTotalCount = 0; UpdateFollowersLabel();
			return;
		}
		FollowersFirstPageCount = Response.Followers.Num();
		FollowersFirstTotalCount = Response.Pagination.Total;
		UpdateFollowersLabel();
	}

	void OnFollowingPage(const FLootLockerListFollowersResponse& Response)
	{
		if (!Response.success)
		{
			UE_LOG(LogTemp, Warning, TEXT("Following count fetch failed: %s"), *Response.Error);
			FollowingFirstPageCount = 0; FollowingFirstTotalCount = 0; UpdateFollowingLabel();
			return;
		}
		FollowingFirstPageCount = Response.Followers.Num();
		FollowingFirstTotalCount = Response.Pagination.Total;
		UpdateFollowingLabel();
	}

	void UpdateFollowersLabel()
	{
		if (FollowersFirstPageCount < 0) return; // not ready
		UProfileHeaderWidget::SetFollowersCount(FString::Printf(TEXT("%d/%d"), FollowersFirstPageCount, FollowersFirstTotalCount));
	}

	void UpdateFollowingLabel()
	{
		if (FollowingFirstPageCount < 0) return;
		UProfileHeaderWidget::SetFollowingCount(FString::Printf(TEXT("%d/%d"), FollowingFirstPageCount, FollowingFirstTotalCount));
	}
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Follow a Player

Trigger from a profile card, leaderboard row, chat user badge, or “Suggested Players” carousel.

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

```csharp
// Follow Player (Optimistic UI) Example
// Goal: Player taps a Follow button on another player's profile card.
// We immediately reflect the new state in UI, then roll back if the API fails.
//
// Assumptions:
// - UI elements: UIButton FollowButton; UILabel FollowLabel
// - Local cache: HashSet<string> FollowingIds storing public UIDs we follow
// - Method ShowToast(string msg) for transient user feedback
// - playerPublicUID is the target player's public UID

public class FollowActionController
{
	private readonly HashSet<string> _followingIds;
	public FollowActionController(HashSet<string> followingIds) => _followingIds = followingIds;

	public void OnFollowButtonClicked(string playerPublicUID)
	{
		if (string.IsNullOrEmpty(playerPublicUID)) return;
		if (_followingIds.Contains(playerPublicUID))
		{
			// Already following (button might have been double-clicked)
			ShowToast("Already following");
			return;
		}

		// Optimistic state change
		SetButtonStateLoading();
		SetVisualStateFollowing();
		_followingIds.Add(playerPublicUID);

		LootLockerSDKManager.FollowPlayer(playerPublicUID, (resp) =>
		{
			if (!resp.success)
			{
				// Rollback
				_followingIds.Remove(playerPublicUID);
				SetVisualStateFollow();
				ShowToast("Follow failed: " + resp.errorData?.message);
				return;
			}

			// Success: Optionally increment a displayed count
			PlayerProfileHeader.IncrementFollowingCount(1);
			ShowToast("Now following player: " + playerPublicUID);
		});
	}

	private void SetVisualStateFollowing()
	{
		FollowButton.interactable = false; // Optionally re-enable later as an unfollow button
		FollowLabel.text = "Following";
	}

	private void SetVisualStateFollow()
	{
		FollowButton.interactable = true;
		FollowLabel.text = "Follow";
	}

	private void SetButtonStateLoading()
	{
		FollowLabel.text = "..."; // Brief loading indicator
	}
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Follow Player (Optimistic) - Unreal C++
// Demonstrates using ULootLockerSDKManager::FollowPlayer with an optimistic UI state.
// Assumptions:
// - You maintain a TSet<FString> FollowingIds of public UIDs the local player follows.
// - UPlayerProfileWidget exposes: SetFollowButtonState(EFollowState) and ShowToast(FString)
// - EFollowState is a small enum you define: { Follow, Loading, Following }

class FFollowAction
{
public:
	TSet<FString> FollowingIds;

	void Follow(const FString& TargetPublicUid)
	{
		if (TargetPublicUid.IsEmpty()) return;
		if (FollowingIds.Contains(TargetPublicUid))
		{
			UPlayerProfileWidget::ShowToast(TEXT("Already following"));
			return;
		}

		// Optimistic state
		UPlayerProfileWidget::SetFollowButtonState(EFollowState::Loading);
		UPlayerProfileWidget::SetFollowButtonState(EFollowState::Following); // Immediately show success state
		FollowingIds.Add(TargetPublicUid);

		ULootLockerSDKManager::FollowPlayer(TargetPublicUid,
			FLootLockerFollowActionResponseDelegate::CreateLambda([PublicUID=TargetPublicUid, &](const FLootLockerFollowActionResponse& Response) {
                
                if (!Response.success)
                {
                    // Rollback: attempt to remove. We would have needed to store which target we just added if multiple calls can be concurrent.
                    // For simplicity, assume only one in-flight follow at a time.
                    FollowingIds.Remove(PublicUID);
                    UPlayerProfileWidget::SetFollowButtonState(EFollowState::Follow);
                    UPlayerProfileWidget::ShowToast(FString::Printf(TEXT("Follow failed: %s"), *Response.Error));
                    return;
                }

                UPlayerProfileWidget::ShowToast(TEXT("Now following"));
                UPlayerProfileHeaderWidget::IncrementFollowingCount(1); // Optional
            });
	}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

### Optimistic Updates

1. Disable the button and swap label to “Following…”
2. Send follow request
3. On success: set to “Following” (or icon toggle)
4. On failure: revert and show a subtle toast

## Unfollow a Player

Offer this option from the “Following” list or a context / overflow menu.

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

```csharp
// Unfollow Player Example (with optional confirm + undo)
// Assumptions:
// - _followingIds HashSet<string>
// - UI provides a confirmation popup Confirm("Unfollow X?", onYes, onNo)
// - ShowUndo(message, action) displays a snackbar with Undo button
// - PlayerProfileHeader.DecrementFollowingCount(int)

public class UnfollowActionController
{
	private readonly HashSet<string> _followingIds;
	public UnfollowActionController(HashSet<string> followingIds) => _followingIds = followingIds;

	public void RequestUnfollow(string targetPublicUID, string targetDisplayName)
	{
		if (!_followingIds.Contains(targetPublicUID)) return; // Already not following

		Confirm($"Unfollow {targetDisplayName}?", () => ExecuteUnfollow(targetPublicUID, targetDisplayName), () => { /* canceled */ });
	}

	private void ExecuteUnfollow(string targetPublicUID, string targetDisplayName)
	{
		// Optimistic removal
		_followingIds.Remove(targetPublicUID);
		PlayerProfileHeader.DecrementFollowingCount(1);
		UpdateButtonToFollow();

		LootLockerSDKManager.UnfollowPlayer(targetPublicUID, (resp) =>
		{
			if (!resp.success)
			{
				// Rollback
				_followingIds.Add(targetPublicUID);
				PlayerProfileHeader.DecrementFollowingCount(-1); // undo decrement
				UpdateButtonToFollowing();
				ShowToast("Unfollow failed: " + resp.errorData?.message);
				return;
			}

			// Provide Undo for a short window
			ShowUndo($"Unfollowed {targetDisplayName}", () => UndoUnfollow(targetPublicUID));
		});
	}

	private void UndoUnfollow(string targetPublicUID)
	{
		if (_followingIds.Contains(targetPublicUID)) return; // Already re-added somehow
		_followingIds.Add(targetPublicUID);
		PlayerProfileHeader.DecrementFollowingCount(-1);
		UpdateButtonToFollowing();
		// Optionally silently call FollowPlayer again (depends if you want immediate server re-sync)
		LootLockerSDKManager.FollowPlayer(targetPublicUID, _ => {});
	}

	private void UpdateButtonToFollow() => FollowLabel.text = "Follow"; // pseudo
	private void UpdateButtonToFollowing() => FollowLabel.text = "Following";
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Unfollow Player - Unreal C++
// Shows confirmation, optimistic removal, rollback on failure.
// Assumptions:
// - TSet<FString> FollowingIds cache
// - UFollowingListWidget::RemoveRow(FString PublicUid)
// - UPlayerProfileWidget::SetFollowButtonState(EFollowState)
// - ShowConfirm/Dialog & ShowToast pseudo helpers

class FUnfollowAction
{
public:
	TSet<FString> FollowingIds;

	void RequestUnfollow(const FString& TargetPublicUid, const FString& DisplayName)
	{
		if (!FollowingIds.Contains(TargetPublicUid)) return;
		ShowConfirm(FString::Printf(TEXT("Unfollow %s?"), *DisplayName),
			FSimpleDelegate::CreateRaw(this, &FUnfollowAction::ExecuteUnfollow, TargetPublicUid));
	}

private:
	void ExecuteUnfollow(FString TargetPublicUid)
	{
		// Optimistic state: remove from cache & UI
		FollowingIds.Remove(TargetPublicUid);
		UFollowingListWidget::RemoveRow(TargetPublicUid);
		UPlayerProfileWidget::SetFollowButtonState(EFollowState::Follow); // Button reverts to follow

		// Keep a copy for rollback
		PendingUnfollowTarget = TargetPublicUid;

		ULootLockerSDKManager::UnfollowPlayer(TargetPublicUid,
			FLootLockerFollowActionResponseDelegate::CreateRaw(this, &FUnfollowAction::OnUnfollowCompleted));
	}

	void OnUnfollowCompleted(const FLootLockerFollowActionResponse& Response)
	{
		if (!Response.success)
		{
			// Rollback: we only tracked the last pending target for simplicity.
			if (!PendingUnfollowTarget.IsEmpty())
			{
				FollowingIds.Add(PendingUnfollowTarget);
				UFollowingListWidget::ReInsert(PendingUnfollowTarget);
				UPlayerProfileWidget::SetFollowButtonState(EFollowState::Following);
			}
			ShowToast(FString::Printf(TEXT("Unfollow failed: %s"), *Response.Error));
			return;
		}
		ShowToast(TEXT("Unfollowed"));
		PendingUnfollowTarget.Empty();
	}

	FString PendingUnfollowTarget; // Simple single in-flight tracking
};
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

### Confirmation Pattern

Use lightweight confirmations only if unfollow has downstream impact (e.g. curated feed). Otherwise allow instant toggle with an Undo snackbar.

## Determining If Current Player Follows Target

To check if the logged-in player follows a specific other player you do NOT need to build or hydrate a full cache. Use the single-item pagination shortcut: call the following-list endpoint with the target player's public UID as the cursor (or path parameter depending on SDK variant) and request `Count: 1`. If one entry is returned, the relationship exists; if zero, it does not.

Why this works: the server returns the players the current player is following starting “from” the supplied cursor (player ULID). Asking for only one result lets the backend tell you immediately if that specific ID is in the set without scanning client‑side.

Pros:

* O(1) network request per check (no growing local data structure)
* Constant payload size (either 0 or 1 item)
* Always fresh (no stale cache issues)

Consider caching only if you batch many checks in the same frame (e.g. rendering 100 profile tiles). For a single profile view, prefer this direct probe.

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

```csharp
// Determine if we follow targetPlayerULID by requesting just one item "starting at" that player.
// If the first returned player matches the target, we are following them.

public static class FollowProbe
{
	public static void CheckFollowing(string targetPlayerULID, System.Action<bool> onResult)
	{
		if (string.IsNullOrEmpty(targetPlayerULID)) { onResult?.Invoke(false); return; }

		// Ask for a single entry starting at the target cursor
		LootLockerSDKManager.ListFollowingPaginated(targetPlayerULID, 1, (resp) =>
		{
			if (!resp.success || resp.following == null || resp.following.Length == 0)
			{
				onResult?.Invoke(false);
				return;
			}

			// Server returned at least one player; see if it is the one we asked about.
			bool isFollowing = resp.following[0].player_id == targetPlayerULID;
			onResult?.Invoke(isFollowing);
		});
	}
}

// Usage:
// FollowProbe.CheckFollowing(otherPlayerUlid, (isFollowing) => UIProfileButton.SetState(isFollowing));
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Single-player follow probe using paginated ListFollowing with a cursor of the target player.

class FFollowProbe
{
public:
	static void CheckFollowing(const FString& TargetPlayerULID, TFunction<void(bool)> Callback)
	{
		if (TargetPlayerULID.IsEmpty()) { Callback(false); return; }

		ULootLockerSDKManager::ListFollowingPaginated(TargetPlayerULID, 1,
			FLootLockerListFollowersResponseDelegate::CreateLambda([Callback, TargetPlayerULID](const FLootLockerListFollowersResponse& Response)
		{
			if (!Response.success || Response.Followers.Num() == 0)
			{
				Callback(false);
				return;
			}
			const bool bIsFollowing = Response.Followers[0].Publicuid == TargetPlayerULID;
			Callback(bIsFollowing);
		}));
	}
};

// Usage:
// FFollowProbe::CheckFollowing(OtherPlayerUid, [](bool bIsFollowing){ UProfileWidget::SetFollowState(bIsFollowing); });
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Coming soon...
{% endtab %}

{% tab title="REST" %}
Coming soon...
{% endtab %}
{% endtabs %}

## Performance Tips

* Debounce rapid follow toggles (e.g. leaderboards) to one in-flight request per target.
* Paginate aggressively (e.g. 25–50 per page) for scrolling lists.
* Preload the first page of “Following” at session start if many UI surfaces need that state.
* Avoid full refresh after each follow action; surgically mutate local structures.

## Example Feature Ideas

* Activity Feed: Show recent achievements of players you follow.
* “Players Following This Creator Also Follow…” recommendations (intersect follower sets).
* Seasonal Follow Goals: Reward cosmetics when a player reaches follower milestones.

## Conclusion

In this How-to we listed followers, listed who a player is following, implemented follow/unfollow actions, handled pagination, and optimized UI responsiveness with caching and optimistic updates. You can now extend this to social feeds, recommendations, or creator-style profile experiences. Next, explore adding [Friends](/players/friends-and-followers/how-to/use-friends-in-game) or managing relationships in the [Web Console](/players/friends-and-followers/how-to/manage-relationships-web-console).


# Inventory

LootLocker’s Inventory system manages the list of assets a player owns, accessible through both the API and the Web Console. Assets can be added to a player's inventory via various systems, such as rewards from leaderboards, progressions, or triggers; purchases made through catalogs; or inclusion in the default loadout of a class or hero. The Web Console also allows administrators to manually gift assets to players or process refunds for purchased items.

Each asset in the inventory is stored as an Asset Instance, representing a unique version of the asset with its own Instance ID. This ensures detailed tracking and precise management of individual items.

### Use Cases

* Player inventory system
* Character / Hero inventories
* Refunding purchased assets
* Grant assets manually

### How-to

* [Work with Player Inventory](/players/inventory/how-to/player-inventory)


# How To


# Work with Player Inventory

### Retrieve Inventory in Game

To retrieve the inventory of player in game use the following SDK code

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

```csharp
LootLockerSDKManager.GetInventory((response) =>
{
    if (response.success)
    {
        Debug.Log("Successfully retrieved player inventory: " + response.inventory.Length);
    } else
    {
        Debug.Log("Error getting player inventory");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/ONTGBWkpG7WlVgZ4JfTi" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/4o3yt2r-">Blueprint example of retrieving the players inventory</a></p></figcaption></figure>

To copy and paste the above example into your editor, [look here](https://blueprintue.com/blueprint/4o3yt2r-/).

**Input**

You need to exchange the white empty event for whatever event you want to use to trigger this flow.

**Output**

We recommend branching the completed events on the success flag in the response, and if you do this, you will probably want to add error handling in case the request fails as well as what (if any) continued actions you want on success.
{% endtab %}

{% tab title="REST" %}

```bash
curl -X GET "https://api.lootlocker.io/game/v1/player/inventory/list" \
  -H "x-session-token: your_token_here"
```

Example response:

```json
{
    "success": true,
    "inventory": [
        {
            "instance_id": 538,
            "variation_id": 45,
            "rental_option_id": null,
            "acquisition_source": "grant_default_loadout",
            "acquisition_date": "2022-05-30T07:57:58+00:00",
            "asset": {...},
            "rental": {
                "is_rental": false,
                "time_left": null,
                "duration": null,
                "is_active": null
            }
        }
    ]
}
```

{% endtab %}
{% endtabs %}

### Gift an Asset

Gifting is the ability to grant a specific Asset to a player directly through the LootLocker Management Console.

![](/files/9e9CzotvJCH98g7emGxx)

To gift an Asset to a player, click the `Give Asset` button in the Player Manager interface. Search for and select an Asset from the popup Asset list. This will immediately grant the player the Asset. A list of all granted Assets can be found in the Orders tab in the Player Manager interface.

![](/files/rI6E7bya0pz4EeRLRJzj)

### Refund an Order

Refunds can be used in case a player purchases an Asset unintentionally or needs to be refunded their purchase for other reasons. Refunding an Order in LootLocker is an easy and simple process.

![](/files/-MXl_fIoDzR1ngOPyNom)

To refund an Order, navigate to the Inventory tab in the Player Manager interface. Find the Asset in question and use the `Refund` button to refund the Asset. This will immediately refund the purchased Assets and return the Currency to the player’s wallet. If the player purchased an Asset Package, you will need to refund all Assets from that package for the Currency to be returned to the player's wallet.

{% hint style="info" %}
Only purchased Assets can be refunded. Assets granted through other LootLocker systems cannot be refunded.
{% endhint %}


# Names

The Player Names system in LootLocker provides a flexible way to assign and manage player profile names within your game. Names can be set by the players themselves or automatically assigned based on the platform they are playing on. For platforms like Steam or PlayStation, where players already have an existing name, LootLocker will automatically attach the name to the player when a session begins.

If your game allows players to set their own names, you can enable a unique name restriction to ensure that each player has a distinct identity. Additionally, player names can be updated directly through the web console, offering convenient management options for game administrators.

### Use Cases

* Display player’s platform name in-game
* Let players create their own name
* Unique player names

### How-to

* [Work with Player Names](/players/names/how-to/player-names)


# How To


# Work with Player Names

When possible, LootLocker will attach a name to the player when starting a session.\
This however, is only possible for platforms where the player has a name already, such as Steam or PlayStation Network.

**Platforms Supporting Automatic Names**

Platforms where the name is automatically retrieved when starting a session:

* Steam
* PlayStation Network

If you're using a platform without automatic player names, or want to update the name LootLocker has stored for the player, it's possible to change the name using the SDK or API.

Player names can also be viewed in the web console:

![Players with names in the web console](/files/-Mk2_BSZutL5Tdlqvn3I)

#### Unique Player Names

If you want to ensure that no two players in your game has the same name, you can use the `Unique Player Names` setting in your [games settings](https://console.lootlocker.com/settings/game).

Enabling this toggle, will return an error when updating a players name, if that name is already taken.

#### Update Player Name in Game

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

```csharp
LootLockerSDKManager.SetPlayerName("Some other name", (response) =>
{
    if (response.success)
    {
        Debug.Log("Successfully set player name");
    } else
    {
        Debug.Log("Error setting player name");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/2J5N2C5VD27lwmkchATv" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/dv4kl079/">Blueprint example of updating player name</a></p></figcaption></figure>
{% endtab %}

{% tab title="Godot" %}

```gdscript

var response = await LL_Players.SetPlayerName.new("a-new-name").send()
if(!response.success) :
    # Request failed, handle errors
    pass
else:
    # Request succeeded, use response as applicable in your game logic
    pass
```

{% endtab %}

{% tab title="REST" %}

```bash
curl -X PATCH "https://api.lootlocker.io/game/player/name" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"name\": \"Player Name\"}"
```

Example response:

```json
{
  "name": "Player Name"
}
```

{% endtab %}
{% endtabs %}

#### Retrieve Player Name in Game

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

```csharp
LootLockerSDKManager.GetPlayerName((response) =>
{
    if (response.success)
    {
        Debug.Log("Successfully retrieved player name: " + response.name);
    } else
    {
        Debug.Log("Error getting player name");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/X8QcA2GnahfXAR8G4Nhm" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/rl9x4o8g/">Blueprint example of getting player name</a></p></figcaption></figure>
{% endtab %}

{% tab title="Godot" %}

```gdscript

var response = await LL_Players.GetPlayersActiveName.new().send()
if(!response.success):
    # Request failed, handle errors
    pass
else:
    # Request succeeded, use response as applicable in your game logic
    pass
```

{% endtab %}

{% tab title="REST" %}

```bash
curl -X GET "https://api.lootlocker.io/game/player/name" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01"
```

Example response:

```json
{
  "name": "Player Name"
}
```

{% endtab %}
{% endtabs %}

#### Lookup Multiple Player Names using Player IDs

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

```csharp
ulong player1ID = 1;
ulong player2ID = 2;
ulong player3ID = 3;

LootLockerSDKManager.LookupPlayerNamesByPlayerIds(new ulong[] { player1ID, player2ID, player3ID }, response =>
{
    if (response.success)
    {
        foreach (var player in response.players)
        {
            Debug.Log(player.player_id);
            Debug.Log(player.player_public_uid);
            Debug.Log(player.name);
            Debug.Log(player.last_active_platform);
            Debug.Log(player.platform_player_id);
        }
    } else
    {
        Debug.Log("Error looking up player names");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/hOGdISLjpX47NoibSDDL" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/m4kdbavd/">Blueprint example of getting multiple player names</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -G "https://api.lootlocker.io/game/player/lookup/name" \
  -H "x-session-token: your_token_here" \
  -H "LL-Version: 2021-03-01" \
  -d player_id=1 \
  -d player_id=2 \
  -d player_public_uid=JARL7PGR \
  -d player_guest_login_id=a270686a-7dd7-482f-89b6-9b2a634f46fb \
  -d steam_id=9465748036854778475 \
  -d psn_id=1234567890 \
  -d xbox_id=E51D19530BBE721286F75C03B934E5EB7CA23B99
```

Example response:

```json
{
  "players": [
    {
      "player_id": 1,
      "player_public_uid": "6DDXH947",
      "name": "Player 1 Name",
      "last_active_platform": "xbox_one"
    },
    {
      "player_id": 2,
      "player_public_uid": "4FDGF738",
      "name": "Player 2 Name",
      "last_active_platform": "xbox_one"
    },
    {
      "player_id": 3,
      "player_public_uid": "JARL7PGR",
      "name": "Player 3 Name",
      "last_active_platform": "guest"
    },
    {
      "player_id": 4,
      "player_public_uid": "9HDK4F5Y",
      "name": "Player 4 PSN Name",
      "last_active_platform": "psn",
      "platform_player_id": "1234567890"
    },
    {
      "player_id": 5,
      "player_public_uid": "3XTMHFS3",
      "name": "Player 5 Steam Name",
      "last_active_platform": "steam",
      "platform_player_id": "9465748036854778475"
    },
    {
      "player_id": 6,
      "player_public_uid": "9RKPSRRT",
      "name": "Player 6 XBox Name",
      "last_active_platform": "xbox_one",
      "platform_player_id": "E51D19530BBE721286F75C03B934E5EB7CA23B99"
    },
    {
      "player_id": 7,
      "player_public_uid": "T4HV7G5D",
      "name": "Player 7 GuestLogin Name",
      "last_active_platform": "guest",
      "platform_player_id": "a270686a-7dd7-482f-89b6-9b2a634f46fb"
    }
  ]
}
```

{% endtab %}
{% endtabs %}

Multiple platforms and public UID is also supported:

```csharp
- LookupPlayerNamesByPlayerPublicUIds(string[] playerPublicUIds, Action onComplete)
- LookupPlayerNamesBySteamIds(ulong[] steamIds, Action onComplete)
- LookupPlayerNamesBySteamIds(string[] steamIds, Action onComplete)
- LookupPlayerNamesByPSNIds(ulong[] psnIds, Action onComplete)
- LookupPlayerNamesByPSNIds(string[] psnIds, Action onComplete)
- LookupPlayerNamesByXboxIds(string[] xboxIds, Action onComplete)
```


# Player Manager

The LootLocker Player Manager is designed for managing player data both in-game and through the web console. In the Web Console, you can easily search for players to view their profiles, displaying key identifiers like game-specific and platform IDs, as well as any connected content and game systems, such as inventory and progressions.

### Use Cases

* Customer support resolving purchases and rewards
* Quality assurance testing game system
* GDPR and privacy requests
* Testing platform integrations
* Player account auditing
* Banning players
* Sending notifications

### How-to

* [Manage Players through Web Console](/players/managing-players/how-to/player-console-overview)
* [Manage Players through Code](/players/managing-players/how-to/manage-players)
* [Use Player Operations](/players/managing-players/how-to/player-operations)
* [Ban Players](/players/managing-players/how-to/ban-players)
* [Send Player Notification](/players/managing-players/how-to/send-player-notification)


# How To


# Manage Players through Web Console

The Player Manager can be reached by click on the player icon on left side bar of the web console.

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

#### Player Search

The player manager will automatically load your most recent players. You can search for your players in the Player Manager by entering a players platform ID (SteamID, PSN ID etc) or their Public UID in the search field and clicking the Search button.

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

Click the View button on a search profile to load the Player Page.

#### Player Page

The Player Page displays all relevant information of the player you have selected including their assets, orders, and storage.

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

**Player Information**

You can find basic information about the player at the top of the Player Page. This area includes the following information:

* **Name:** The Player’s profile name they set in the first party platform. Clicking on the name will take you to the platform’s player page (if available)
* **Avatar:** The Player’s avatar they set in the first party platform (if available)
* **Public UID:** A public identifier used by players to identify themselves
* **Player ID:** An identifier used within the APIs
* **First Seen:** The date on which the player first played your game
* **Last Seen:** The most recent date when the player played your game
* **Currencies:** The total values of currencies the player has in their account

**Profile Visibility**

Set if the player’s profile is visible to the public or not. This affects integrations such as Steam Inventories and Marketplace. Neither will work for the player if the player’s profile is set to private.

**Delete Profile**

Click the Delete Profile button to anonymize the player account. This operation cannot be reversed.

**Platforms**

The platforms field will display all platform accounts linked to this player profile.

#### Inventory

The inventory section of the Player Page lists all assets within the player’s inventory and the date that the Asset was added to the inventory. The inventory can be searched and assets can be quickly viewed through their corresponding link.

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

**Give Asset**

Click the Give Asset button in order to manually add an asset to the player's profile. Clicking this brings up a window that allows you to search through all of your game’s assets.

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

**Refund**

An asset purchased using in-game currency can be refunded by clicking the Refund button. This will remove the asset from the player's inventory and refund the currency amount paid for it to the player's wallet.

#### Orders

The Orders section of the Player Page lists all asset grants that have taken place within the player’s inventory. These orders can be from store purchases or other events where the player has had an asset added to their inventory. Each order can be inspected to view the full details of the order. Orders with a green value indicate the player has received assets and/or currency in their order. Orders with a red value indicates the player has spent currency to purchase an asset.

<figure><img src="/files/5FusAAFB6WKgQs4C32uw" alt=""><figcaption></figcaption></figure>

#### Storage

The Storage section of the Player Page displays all Key/Value pairs that are stored by your game in relation to the player. New Key/Value pairs can be created by clicking the Add Storage button. Existing pairs can be edited or deleted, or made public and readable by other players.

<figure><img src="/files/3rpfjsPRu99vSoOjRP26" alt=""><figcaption></figcaption></figure>

#### Progression

The Progression section of the Player Page lets you view the amount of XP the player has earned in your [progression](https://github.com/lootlocker/gitbook-sync/blob/main/game-systems/progressions/set-up-progressions/README.md) system. Here you can also add XP to the player's progression by clicking the Add button. You can also Reset the player's progression by clicking the Reset XP button.

<figure><img src="/files/3X2dE1qnnqyx3YU7NLlT" alt=""><figcaption></figcaption></figure>

#### Leaderboards

The Leaderboards section of the Player Page shows you which [leaderboard](https://github.com/lootlocker/gitbook-sync/blob/main/players/game-systems/leaderboards/README.md) the currently selected player has submitted scores in. The menu displays the Leaderboard Name, ID, the player's Rank in the leaderobard, and their score entry.

In this menu you can also edit or delete individual entries the player has made to different leaderboards.

**Remove Player From All Leaderboards**

To remove the player from all leaderboards, click the Remove Player From All Leaderboards button. This action is not reversible.

#### Files

The Files section of the Player Page shows you which files have been uploaded to the player's profile. Files can be used to store specific player config files or save game files in case you want to use LootLocker as a cloud save service.


# Manage Players In-Game

### Search for Players

To search for a player, simply enter their Platform ID into the search bar and press Enter. If the player, or multiple players are found, LootLocker will display their profiles below the search bar. Click `View` to load the player's profile.

![](/files/-Mk1cbGcAr3aNphva33a)

### Retrieving player information

From the web console you can use the view button on players to view a specific player.

![](/files/-Mk2b2dSVinCQysU82wu)

From here it's possible to give players assets and change player storage items.

#### Retrieve Player Information in Game

Retrieving player info from your game returns information about level and account balance. See our [progression how-to](https://github.com/lootlocker/gitbook-sync/blob/main/game-systems/progressions/set-up-progressions/use-a-progression-system-in-game.md) for how to use this in the SDK

### Public/Private Profile

This is currently used to disable any inventory integration with Steam.

Setting a players profile to private also hides their Steam items attached to your game from their Steam profile.

![Switch between public and private profile](/files/-Mk1bxgSgPgCiGnjatDJ)

#### Change Public/Private Profile in Game

See the following examples for how to change from/to public and profile for a player.

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

```csharp
// Set to private
LootLockerSDKManager.SetProfilePrivate((response) =>
{
    if (response.success)
    {
        Debug.Log("profile successfully to private");
    }
    else
    {
        Debug.Log("failed setting profile to private");
    }
});

// Set to public
LootLockerSDKManager.SetProfilePublic((response) =>
{
    if (response.success)
    {
        Debug.Log("profile successfully to public");
    }
    else
    {
        Debug.Log("failed setting profile to public");
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/HgYDw2DpDCq9detrCpl3" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/9j9gd_3t/">Blueprint example of setting profile to private</a></p></figcaption></figure>

<figure><img src="/files/h4fY3OFtZJzrd0T4qtTD" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/89hthn82/">Blueprint example of setting profile to public</a></p></figcaption></figure>
{% endtab %}

{% tab title="Godot" %}

```gdscript

# Set to private
var response = await LL_Players.SetPlayerProfilePublic.new().send()
if(!response.success) :
    # Request failed, handle errors
    pass
else:
    # Request succeeded, use response as applicable in your game logic
    pass

# Set to public
var response = await LL_Players.SetPlayerProfilePrivate.new().send()
if(!response.success) :
    # Request failed, handle errors
    pass
else:
    # Request succeeded, use response as applicable in your game logic
    pass
```

{% endtab %}

{% tab title="REST" %}
**Set to Public**

```bash
curl -X POST "https://api.lootlocker.io/game/v1/player/profile/public" \
  -H "x-session-token: your_token_here"
```

Example response:

```json
{
  "success": true
}
```

**Set to Private**

```bash
curl -X DELETE "https://api.lootlocker.io/game/v1/player/profile/public" \
  -H "x-session-token: your_token_here"
```

Example response:

```json
{
  "success": true
}
```

{% endtab %}
{% endtabs %}


# Use Player Operations

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

Each player that has started a Session will now also have Operations. \\

### What is Operations?

Operations are to help you keep track of in-game purchases and/or Balance changes.

You will be able to see when a purchase was started and the result.

<figure><img src="/files/8wEMJs54g8sKvxCUbx82" alt=""><figcaption></figcaption></figure>

### Metadata

Depending on the Operation Code, the content may vary. Lets take balance\_credit as an example.

This consist data such as the amount credited, what currency was credited, to which wallet and which player. Source shows where this Operation came from.

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


# Ban Players

This how-to demonstrates how to use the Ban Player feature to prevent a player from starting new game sessions. This can be useful for moderating players who violate community guidelines, exploit the game, or otherwise disrupt the experience. When a player is banned, they will no longer be able to start a new session in the game.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* An existing game in the [Web Console](https://console.lootlocker.com/) wtih at least 1 player profile

### Ban a Player

You can ban a player directly from their profile in the LootLocker [Web Console](https://console.lootlocker.com/).

To do this, navigate to [Player Manager](https://console.lootlocker.com/players) in the Web Console and search for and open the player profile you want to manage.

Click the Actions dropdown and Select Ban Player.

Select an optional time duration for the ban using the time and date picker. If no time is selected, the ban will be indefinite.

Add an optional note describing the reason for the ban. This is used for internal purposes only.

Confirm the action by clicking Ban Player.

![](/files/qoai0zkGfdIhrS9ZCe9g)

Once banned, the player will be prevented from starting new sessions.

### Viewing Banned Status

If a player has been banned, their player profile will clearly display that the player is banned. In this case, you have a few options:

* View and edit the notes associated with the ban by clicking View Notes
* Unban the player if the restriction should be lifted

![](/files/qD9T4rNDAgV4waTwFNcn)

### Unban a Player

To remove a ban from a player profile, navigate to the player profile you wish to unban.

Click the Unban button.

The player will now be able to start game sessions sessions again. Any notes associated with the ban will also be removed.

### Conclusion

In this how-to we have banned a player, reviewed their ban notes, and unbanned them.


# Send Player Notification

In this how-to we will send individual player notifications through the web console.

In addition to [notifications generated automatically by other LootLocker systems](/shared-systems/notifications) (such as Triggers, Catalog Purchases, Twitch Drops, or the Admin API), LootLocker also allows you to send notifications directly to individual players from the Web Console.

This can be useful for tasks such as:

* Thanking a player for submitting feedback
* Informing a player about a successful refund
* Communicating game-specific information to a specific player

Notifications sent this way are added to the player’s notification queue and can be retrieved by the game client like any other LootLocker notification.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* An existing game in the [Web Console](https://console.lootlocker.com/)

### Sending a Notification

To send a notification to a player, navigate to Player Manager in the Web Console and select the player you want to notify.

![](/files/QJqav59IRbHBIx3waPac)

Click the Actions dropdown menu and select Send Notification. A modal will appear where you must provide the following information:

* **Notification Type:** Used by the game client to identify what type of notification is being sent.
* **Priority:** Indicates how urgent the notification is. The game client can use this value to determine which notifications should be shown first.
* **Content:** JSON payload that will be delivered with the notification and parsed by the game client.
* **Properties:** Optional key–value pairs that can be attached to the notification.

Once the fields are filled out, click Send. The notification will be added to the player’s Notification Queue, where it can be retrieved by the game client.

### Viewing Notification Status

You can view notifications sent to a player directly from their profile page by navigating to the Notifications tab in the player profile menu.

![](/files/pXzutAalOoAqrlhME7q6)

This will display all notifications sent to the player through the Web Console, along with their read status, allowing you to confirm whether the player has retrieved the notification.

### Conclusion

In this how-to we have learned how to send notifications and view their status. Visit the documentation for the [Notification shared system](/shared-systems/notifications) to learn how to implement it in your game.


# Presence

Presence allows you to track when players are actively playing your game across any platform in real time. With basic Presence enabled, LootLocker records when a player starts and ends a session, automatically calculating concurrent users (CCU) and session length.

For teams who need deeper insight into what players are actually doing inside the game, Rich Presence extends this foundation by allowing you to define custom player statuses. These statuses can represent actions or locations such as Main Menu, Gameplay, Pause Menu, Store, Level 3, or any in-game state you choose. LootLocker then constructs a timeline of each session, giving you granular visibility into how players interact with your game.

{% hint style="warning" %}
Rich Presence is currently available only to select paying customers. If you’re interested in enabling it for your game or studio, [please reach out to us via email](mailto:hello@lootlocker.com?subject=I'm%20interested%20in%20Rich%20Presence).
{% endhint %}

### Use Cases

* Track CCU across games and platforms to see real-time player counts in one place
* Measure accurate playtime using true session start and end data
* Understand player habits by viewing when and how long they play
* Monitor progression in real time by tracking time spent in specific states or levels (Rich Mode only)
* Identify friction points by spotting where players slow down or drop off (Rich Mode only)
* Support players more effectively with access to their session timelines (Rich Mode only)

### How-to

* [Enable Presence in Web Console](/players/presence/how-to/enable-presence-in-web-console)
* [View Presence in Web Console](/players/presence/how-to/view-presence-in-web-console)
* [Implement Presence In-Game](/players/presence/how-to/enabling-presence-in-game)
* [Using Rich Presence In-Game](/players/presence/how-to/using-rich-presence-in-game)


# How To


# Enable Presence in Web Console

This how-to walks you through enabling Presence for your game in the LootLocker Web Console.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [An existing game in the Web Console](https://docs.lootlocker.com/)

### Presence Settings

To enable Presence for your game, navigate to Player Operations → Player Settings and select the Presence tab.

![](/files/HzhKXV8WDczyl5wkqd5c)

Choose between the following options:

#### Enable Presence

Enables basic Presence, allowing LootLocker to report how many players are currently online and playing your game across all platforms.

#### Enable Rich Presence Mode

Unlocks detailed player status tracking, including custom states (e.g., Main Menu, Gameplay, Store) and optional metadata.

{% hint style="warning" %}
Rich Presence is available only to approved paying customers, [contact us](mailto:hello@lootlocker.com?subject=I'm%20interested%20in%20Advanced%20Presence) if you’re interested.
{% endhint %}

Click Save to apply your changes. Presence is now ready to begin recieving player data once [implemented in your game](/players/presence/how-to/enabling-presence-in-game).

### Conclusion

In this how-to we have enabled Presence in the Web Console. To learn how to interpret this information, continue to the [View Presence in Web Console](/players/presence/how-to/view-presence-in-web-console) documentation page.


# View Presence in Web Console

This how-to explains how you can view and interpret Presence data in the LootLocker Web Console.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [At least 1 created game in the web console](https://docs.lootlocker.com/)
* [A game with LootLocker and Presence implemented](https://github.com/lootlocker/gitbook-sync/blob/main/players/presence/how-to/enabling-precense-in-game.md)
* At least one online player

### Understanding Presence

To view online players, navigate to Player Operations → Search → Presence. This page displays a list of all players currently online and actively connected to your game.

![](/files/u7KY7fGOxJGHANoJ9yd1)

#### Refreshing Presence Data

**Manual Refresh**

Click Refresh to reload Presence data, updating session durations, statuses, and timelines.

**Auto-Refresh**

Toggle Auto-refresh to automatically update data every 10 seconds.

#### Basic Presence (no Rich Presence enabled)

You will see the following details for each online player:

* **Name:** The player’s name (if set)
* **Public UID:** The player’s unique LootLocker identifier
* **Platforms:** Platforms linked to the player’s account
* **Live Status:** This will only display that the player is online, unless Rich Presence is enabled
* **Last Seen:** The last time the player had an active session
* **First Seen:** When the player first connected to your game

#### Rich Presence Enabled

Click the chevron next to a player's name to expand their entry and view the Session Status Timeline.

![](/files/8gX4rNam4ACSKDDhHxL3)

**Session Status Timeline**

The Session Status Timeline provides a live, visual breakdown of a player's current session, showing each state the player has been in and how long they spent there.

**Player Journey Visualization**

See a chronological view of the player moving through menus, levels, or gameplay states defined by your implementation.

**Hover for Metadata**

If you attach custom key–value data to a status, it appears when hovering over the timeline segment.

**Zoom Controls**

Adjust the timescale to zoom in or out depending on how much detail you want to inspect.

**Status Merging**

When a player sends the same status consecutively, LootLocker automatically merges the entries for clarity.

**Offline Handling**

If a player disconnects, LootLocker keeps the session open for 10 minutes to allow for reconnection (e.g., network dropouts or device restarts). These offline periods appear directly on the timeline.

### Conclusion

In this how-to we've learned how to read player presence information it the LootLocker Web Console.


# Enable Presence in the SDK

This how-to walks you through how to enable presence in your game.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [An existing game in the Web Console](https://docs.lootlocker.com/)
* [Basic Presence Enabled in the Web Console](/players/presence/how-to/enable-presence-in-web-console)

### Enabling Presence

Basic Presence just needs to be enabled in the SDK and the Web Console, after that, playtime data will automatically be sent to LootLocker [to be viewed](/players/presence/how-to/view-presence-in-web-console).

{% tabs %}
{% tab title="Unity" %}
Navigate to **Player Settings** -> **LootLocker SDK**. Select **Enable Presence**

![](/files/RPLN4pgaJzg9CdglgNxh)

#### Presence Settings

After Presence is enabled, you can toggle settings to change some of the behaviour.

* **Auto Connect**
  * Should Presence be connected automatically when a LootLocker Session exists?
  * Can be controlled at runtime with `LootLockerSDKManager.SetPresenceAutoConnectEnabled()`
  * Presence can be manually connected by calling `LootLockerSDKManager.ForceStartPresenceConnection(<callback>, <optional player ulid>)`
* **Auto Pause Presence**
  * Disconnect Presence when application loses focus or is paused
  * Useful for battery saving
  * Can be controlled at runtime with `LootLockerSDKManager.SetPresenceAutoDiscconnectOnFocusChangeEnabled()`
  * Presence can be manually disconnected by calling `LootLockerSDKManager.ForceStopPresenceConnection(<callback>, <optional player ulid>)`
* **Enable Presence in Editor**
  * Uncheck this if you do not want development to affect your Presence data.
    {% endtab %}

{% tab title="Unreal" %}
Navigate to **Project Settings** -> **Plugins** -> **LootLocker SDK**. Select **Enable Presence**

![](/files/RhvLSbFQTwUMrVRQRpLG)

#### Presence Settings

After Presence is enabled, you can toggle settings to change some of the behaviour.

* **Auto-connect on Session Start**
  * Should Presence be connected automatically when a LootLocker Session starts?
  * Can be controlled at runtime with `SetPresenceAutoConnectEnabled()`
  * Presence can be manually connected by calling `ForceStartPresenceConnection(<callback>, <optional player ulid>)`
* **Auto-disconnect on Focus Loss**
  * Disconnect Presence when application loses focus or is paused
  * Useful for battery saving
  * Can be controlled at runtime with `SetPresenceAutoDisconnectOnFocusChangeEnabled()`
  * Presence can be manually disconnected by calling `ForceStopPresenceConnection(<callback>, <optional player ulid>)`
* **Enable Presence in Editor**
  * Uncheck this if you do not want development to affect your Presence data.
    {% endtab %}

{% tab title="REST" %}
Presence uses a Websocket connection. First initialize a user session with REST for any aof the [authentication methods](https://ref.lootlocker.com/game/authentication-folder-2066317). When the user is authorized, open up a websocket connection to `ws://domain_key.lootlocker.com/game/presence/v1` with the first message being:

```bash
{"token": "the_signed_in_users_session_token"}
```

You will get back a response saying if you were authenticated or not:

```bash
{"authenticated":true}
```

The web socket will disconnect automatically after 1 minute of no activity. We recommend to send a ping once per minute to cover any network delays or interruptions. If the Websocket is disconnected, you will need to reconnect again.

```bash
{"type":"ping"}
```

Response:

```bash
{"type":"pong"}
```

{% endtab %}
{% endtabs %}

### Interact with the Presence system

For more detailed control, or to read data about the current presence connection there are some interfaces in the SDK you can interact with. The two main use cases are:

* Manual control of when presence connections are made.
* Read information about the presence connection.

#### Manual Presence Control

{% tabs %}
{% tab title="Unity" %}
**Presence Methods**

If auto connect is disabled in the SDK, then when you have authenticated a user with LootLocker you can at any time call `LootLockerSDKManager.ForceStartPresenceConnection(<callback>, <optional player ulid>)`.

To disconnect, call `LootLockerSDKManager.ForceStopPresenceConnection(<callback>, <optional player ulid>)`.

To disconnect all active connections call `LootLockerSDKManager.ForceStopAllPresenceConnections()`.
{% endtab %}

{% tab title="Unreal" %}
**Presence Methods**

If auto connect is disabled in the SDK, then when you have authenticated a user with LootLocker you can at any time call `ForceStartPresenceConnection(<callback>, <optional player ulid>)`.

To disconnect, call `ForceStopPresenceConnection(<callback>, <optional player ulid>)`.

To disconnect all active connections call `ForceStopAllPresenceConnections()`.
{% endtab %}
{% endtabs %}

#### Presence Connection Information

{% tabs %}
{% tab title="Unity" %}
**Presence Methods**

To check if presence is currently connected for a player, call `LootLockerSDKManager.IsPresenceConnected(<optional player ulid>)`. Or for more granular information, call `LootLockerSDKManager.GetPresenceConnectionState(<optional player ulid>)`.

To see detailed information about the current connection for a player, call `LootLockerSDKManager.GetPresenceConnectionStats(<optional player ulid>)`. This will give you a struct with lots of interesting information, such as the current latency towards LootLocker and how long the connection has been running.
{% endtab %}

{% tab title="Unreal" %}
**Presence Methods**

To check if presence is currently connected for a player, call `IsPresenceConnected(<optional player ulid>)`. Or for more granular information, call `GetPresenceConnectionState(<optional player ulid>)`.

To see detailed information about the current connection for a player, call `GetPresenceConnectionStats(<optional player ulid>)`. This will give you a struct with lots of interesting information, such as the current latency towards LootLocker and how long the connection has been running.
{% endtab %}
{% endtabs %}

### Conclusion

In this how-to we have enabled Basic Presence in the SDK. To learn how to interpret this information, continue to the [View Presence in Web Console](/players/presence/how-to/view-presence-in-web-console) or continue to read up on [Rich Presence](/players/presence/how-to/using-rich-presence-in-game) and it's use cases.


# Using Rich Presence in Game

This how-to walks you through how to use rich presence in your game.

### Prerequisites

* [A LootLocker account](https://lootlocker.com/sign-up)
* [An existing game in the Web Console](https://docs.lootlocker.com/)
* [Rich Presence Enabled in the Web Console](/players/presence/how-to/enable-presence-in-web-console)
* [Presence Enabled in the SDK](/players/presence/how-to/enabling-presence-in-game)

### Rich Presence

Rich Presence does not require any changes for setup in the SDK.

### Sending Status Changes

When sending state changes for a player, you need to provide:

* **A status name**
  * E.g. "main\_menu", "level\_1", "settings\_menu"
* **Metadata** (Optional)
  * You can include metadata when sending the state update
    * E.g. "build\_version", "game\_fps\_count"

{% tabs %}
{% tab title="Unity" %}
Sending a Presence status is as simple as calling a function.

```csharp
// Example code of a function being called when the player enters a Game Over state
public void SendGameOverPresenceStatus()
{
    Dictionary<string, string> presenceDetails = new Dictionary<string, string>()
    {
        {"device", SystemInfo.deviceModel },
        {"unity_version", Application.unityVersion}
    };
    LootLockerPresenceManager.UpdatePresenceStatus("game_over", presenceDetails);
}
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/LPGl8mi9VM9BB2Ch7RPm" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/-0b1h50b/">Blueprint example of updating presence status</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}
Presence uses a Websocket connection. First initialize a user session with REST for any of the [authentication methods](https://ref.lootlocker.com/game/authentication-folder-2066317). When the user is authorized, open up a websocket connection to `ws://domain_key.lootlocker.com/game/presence/v1` with the first message being:

```
{"token": "the_signed_in_users_session_token"}
```

You will get back a response saying if you were authenticated or not:

```
{"authenticated":true}
```

The web socket will disconnect automatically after 1 minute of no activity. We recommend to send a ping once per minute to cover any network delays or interruptions. If the Websocket is disconnected, you will need to reconnect again.

```
{"type":"ping"}
```

Response:

```
{"type":"pong"}
```

To send Rich Presence Statuses, send a message with the following structure:

```
{
    "metadata": {
        "key1": "value1",
        "key2":"value2"
    },
    "status": "game_status_to_send"
}
```

{% endtab %}
{% endtabs %}

### Out of Focus / Pause behaviour

When **Auto Connect** and **Disconnect On Pause** is enabled Presence will reconnect and resend the last status that was sent when Presence reconnects.

### Conclusion

In this how-to we have used Rich Presence to send a change of game status. To learn how to interpret this information, continue to the [View Presence in Web Console](/players/presence/how-to/view-presence-in-web-console).


# Multi User Support

LootLocker now supports Multi User capabilities in both the Unity and Unreal SDKs, allowing your game to manage multiple player sessions at once. This is ideal for games with local multiplayer, hot seat, or any scenario where you need to authenticate and interact with more than one player at a time.

## What is Multi User Support?

Multi User support enables your game to:

* Authenticate and manage multiple players simultaneously.
* Switch between active users or make API calls for specific users.
* Maintain separate sessions and data for each player.

This feature is especially useful for local multiplayer games, shared devices, or games that allow players to switch accounts without restarting the game.

## How It Works

When using Multi User support, the SDK manages a set of player sessions. There is always a "default" player, but you can explicitly specify which player to use for any API call by providing their ULID.

All SDK methods now accept an optional `forPlayerWithUlid` parameter. If you do not specify this, the SDK will use the default player. For advanced scenarios, you should always provide the ULID to ensure the correct player is targeted.

> **Best Practice:** Always provide the ULID when making API calls in a multi user context to avoid ambiguity.

### Active, Inactive, or Cached

There are three *states* to be aware of for local players. When a player starts a lootlocker session using any authentication method the LootLocker SDK will store this data locally and make this session available for subsequent calls. This is now an **active** player. Active in the LootLocker multi user context simply means that "this player has been active this game session".

When the game is restarted but no calls have been made to LootLocker, all players that have previously been active (unless the session has been ended or cache cleared) are considered **inactive** but still **cached**. That means that the session data is still stored on the device and can be picked up at any time.

**Cached** player data in the LootLocker multi user context means that there is session data about this player on the local device.

## How To Guides

* [How to Manage Multiple Concurrent Users](/players/multi-user/how-to/multiple-concurrent-users)
* [How to Switch Profiles](/players/multi-user/how-to/profile-switching)
* [How to End Sessions and Clean State](https://github.com/lootlocker/gitbook-sync/blob/main/players/multi-user/how-to/end-sessions-and-clean-state.md)
* [How to Use Multi User in a Server Context](/players/multi-user/how-to/multi-user-in-a-server-context)
* [Troubleshooting](/players/multi-user/troubleshooting)


# Troubleshooting

Having trouble with multi user support in LootLocker? Here are some common issues and solutions.

## Common Issues

### 1. API calls return data for the wrong user

* **Solution:** Always specify the ULID when making API calls in a multi user context. If you do not, the SDK will use the default user, which may not be what you expect.

### 2. Session not found or invalid

* **Solution:** First make sure that the request is for the expected player and not a previous and expired session. Next ensure the player has been authenticated and their session is active. If you manually manage sessions (e.g., on a server), make sure you have called `StartSessionManual` with the correct data.

### 3. Removing a user does not clear their data

* **Solution:** Use the SDK's end session methods to properly remove a user's session and data. See the [End Sessions and Clean State](https://github.com/lootlocker/gitbook-sync/blob/main/players/multi-user/end-sessions-and-clean-state.md) guide.

### 4. Unexpected default user

* **Solution:** If you switch profiles, remember that the default user changes. Always specify ULID for clarity.

## Debugging

### How to check which is the current default user

To check which player is currently the default player is a simple operation but can be crucial for when you're debugging your game.

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

```cs
LootLockerSDKManager.GetDefaultPlayerUlid();
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
ULootLockerSDKManager::GetDefaultPlayerUlid();
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/cWt3en3hMisvlV5txkCt" alt=""><figcaption><p>Blueprint example of getting which is the current default player</p></figcaption></figure>
{% endtab %}
{% endtabs %}

### How to check which player a request was executed for

To check which player a request (any LootLocker request) was executed for is a simple operation but can be crucial for you when you're debugging your game. The below example shows how to get this data from the response to getting progression data. But all responses to all LootLocker methods holds this crucial context object which contains the player ulid the request was executed for.

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

```cs
var progression_key = "replace_with_your_key";
var executeRequestForPlayerWithUlid = "replace_with_your_player_ulid";
LootLockerSDKManager.GetProgression(progression_key, response =>
{
    if (!response.success)
    {
        Debug.LogWarning($"Getting progression data for {progression_key} failed: {response.errorData}"
            + $"\n request was executed for player with ulid {response.requestContext.player_ulid}"
            + $" and was expected to be executed for player with ulid {executeRequestForPlayerWithUlid}");
    }
}, executeRequestForPlayerWithUlid);
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
FString progression_key = "replace_with_your_key";
FString executeRequestForPlayerWithUlid = "replace_with_your_player_ulid";
ULootLockerSDKManager::GetProgression(progression_key, FLootLockerProgressionResponseDelegate::CreateLambda([=](const FLootLockerProgressionResponse& response)
{
    if (!response.success)
    {
        UE_LOG(LogWorkingProject, Warning, TEXT("Getting progression data for %s failed: %s"), *progression_key, *response.errorData);
        UE_LOG(LogWorkingProject, Warning, TEXT("Request was executed for player with ulid %s"), *response.requestContext.player_ulid);
        UE_LOG(LogWorkingProject, Warning, TEXT("And was expected to be executed for player with ulid %s"), *executeRequestForPlayerWithUlid);
    }
}), executeRequestForPlayerWithUlid);
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/fOjGTkg21iJUQ1ZACXpm" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/scpcq1n-/">Blueprint example of how to debug execution context</a></p></figcaption></figure>
{% endtab %}
{% endtabs %}

## Best Practices

* Always specify ULID in multi user scenarios.
* Clean up sessions for users who log out or leave, unless you know that you want to keep them for added functionality like profile switching.
* In server contexts, manage session tokens and player data carefully.

## Need More Help?

If you encounter issues not covered here, reach out in our [community Discord](https://discord.lootlocker.io).


# How To


# End Sessions and Manage State

Properly ending player sessions and cleaning up multi user state is important for security and memory management, especially in games where players can log out, switch profiles, or leave the device.

### How it Works

* When a player logs out or you want to remove their session, you should end their session and remove their state from the SDK.
* When the player leaves, but does not log out, you should set them to inactive but keep the cached session data. That way the player can pick the session up at a later time (until the token expires).

### How to Sign Your Player Out

In situations when the player actively signs out/removes their profile from the device you need to end the LootLocker session and clear the cached state for that player. This will mean that they have to authenticate again if they decide to play again on the same device.

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

```cs
// The ulid that you want to end the session for
// In a multi user context it is vital to make sure that you're ending
// the session for the correct player.
string playerUlid = "player_ulid_here";

// Remember to provide the shouldClearLocalState parameter as true
// to clear the local state of the player.
bool bShouldClearLocalState = true;
LootLockerSDKManager.EndSession(endSessionResponse =>
{
    if (endSessionResponse.success)
    {
        Log("Session ended successfully");
    }
    else
    {
        // Handle errors
        Log("Failed to end session, local state not cleared: " + endSessionResponse.errorData);
    }
}, bShouldClearLocalState, playerUlid);
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// The ulid that you want to end the session for
// In a multi user context it is vital to make sure that you're ending
// the session for the correct player.
FString playerUlid = "player_ulid_here";

// EndSession will clear the local state for you
ULootLockerSDKManager::EndSession(FLootLockerDefaultDelegate::CreateLambda([=](const FLootLockerResponse& endSessionResponse)
{
    if (endSessionResponse.success)
    {
        UE_LOG(<LogCategory>, Warning, TEXT("Session ended successfully"));
    }
    else
    {
        UE_LOG(<LogCategory>, Warning, TEXT("Failed to end session: %s"), *endSessionResponse.ErrorData.Message);
    }
}), playerUlid);
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/ViWaw6cnLjorvdbjmuLV" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/z22sgldk/">Blueprint example of ending a LootLocker Session and clearing the state</a></p></figcaption></figure>
{% endtab %}
{% endtabs %}

### How to Manually Clear Player State

In some situations you may want to manually and explicitly clear the local session. This can be for example when the session has expired with LootLocker, the user has finished moving an identity provider from one player to another, or simply because you want to clean up local data.

Note that as opposed to the [End Session Example](#how-to-end-sessions-and-clean-state) this makes no calls to the LootLocker systems. It simply removes all information stored locally about the player(s).

#### For a Specific Player

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

```cs
// The ulid that you want to clear data for
string playerUlid = "player_ulid_here";

LootLockerSDKManager.ClearCacheForPlayer(playerUlid);
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// The ulid that you want to clear data for
FString playerUlid = "player_ulid_here";

ULootLockerSDKManager::ClearCacheForPlayer(playerUlid);
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/vm4hKSmGs2K71KSZUBX7" alt=""><figcaption><p>Blueprint example of manually clearing a specific player's local data</p></figcaption></figure>
{% endtab %}
{% endtabs %}

#### For Everyone

Use this if you want a clean slate on the device. Use this during development to make sure you have a clean slate, or when resetting game settings etc.

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

```cs
LootLockerSDKManager.ClearAllPlayerCaches();
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
ULootLockerSDKManager::ClearAllPlayerCaches();
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/NNxFA7x9026FdGipXSgx" alt=""><figcaption><p>Blueprint example of manually clearing all player's local data</p></figcaption></figure>
{% endtab %}
{% endtabs %}

#### For Everyone *Except* a Specific Player

Use this for example when account linking has finished and the new session token should be the only active one on the device. Or when say a local hot seat session finishes and all visitor users should be removed from the device.

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

```cs
// The ulid that you want to keep data for
string playerUlid = "player_ulid_here";

LootLockerSDKManager.ClearAllPlayerCachesExceptForPlayer(playerUlid);
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// The ulid that you want to keep data for
FString playerUlid = "player_ulid_here";

ULootLockerSDKManager::ClearAllPlayerCachesExceptForPlayer(playerUlid);
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/r1G4avqE0Wz6KUFp9Nq9" alt=""><figcaption><p>Blueprint example of manually clearing all player's local data *except* for a specified player</p></figcaption></figure>
{% endtab %}
{% endtabs %}

### How to Manage Active vs Inactive Players

In situations where a player stops playing *for now* you will likely want to set them to *inactive* in the LootLocker SDK. This means that the session will not be used for requests unless specifically requested.

Note that this makes no calls to the LootLocker backend, and is a non destructive action. No data is lost or altered. It *only* designates if the specified player is considered active or not.

#### For a Specific Player

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

```cs
// The ulid of the player that you want to set to inactive
string playerUlid = "player_ulid_here";

LootLockerSDKManager.SetPlayerUlidToInactive(playerUlid);
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// The ulid of the player that you want to set to inactive
FString playerUlid = "player_ulid_here";

ULootLockerSDKManager::SetPlayerUlidToInactive(playerUlid);
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/TMq9ECfXk788sUdaT8rv" alt=""><figcaption><p>Blueprint example of setting a local player to inactive</p></figcaption></figure>
{% endtab %}
{% endtabs %}

#### For Everyone

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

```cs
LootLockerSDKManager.SetAllPlayersToInactive();
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
ULootLockerSDKManager::SetAllPlayersToInactive();
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/CHC3bUdQ6PsO74Ttvn9z" alt=""><figcaption><p>Blueprint example setting all local players to *inactive*</p></figcaption></figure>
{% endtab %}
{% endtabs %}

#### For Everyone *Except* a Specific Player

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

```cs
// The ulid that you want to keep active
string playerUlid = "player_ulid_here";

LootLockerSDKManager.SetAllPlayersToInactiveExceptForPlayer(playerUlid);
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// The ulid that you want to keep active
FString playerUlid = "player_ulid_here";

ULootLockerSDKManager::SetAllPlayersToInactiveExceptForPlayer(playerUlid);
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/BnHIPQNCAjhMqsGp9cCQ" alt=""><figcaption><p>Blueprint example of setting all players to inactive *except* for a specified player</p></figcaption></figure>
{% endtab %}
{% endtabs %}


# Profile Switching

Profile switching allows you to change which player is considered the "default" user in the SDK. This is useful if your game UI or logic is focused on one player at a time, but you have multiple authenticated users similar to classical console or media streaming profile switching.

Use this for example when the game is played by only one player at a time (locally), but multiple people can share the device. That way, at the beginning of the game session, you ask the player to choose a profile (or sign in a new one). Once that is done, all subsequent requests are made on behalf of that player until the game session ends.

### How it Works

* The SDK maintains a default user, which is used for API calls if you do not specify a ULID.
* You can switch the default user to another authenticated player by their ULID.
* This does not log out or remove any users; it only changes which user is considered default (used in calls to LootLocker when no player ULID is specified).

### Example: List Previously Logged in Users

In a profile switching scenario, likely you'll want to display a profile selection screen in the beginning of the game session. This is an implementation example of listing data about all previous players stored in the SDK.

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

```cs
var cachedPlayerUlids = LootLockerSDKManager.GetCachedPlayerUlids();

foreach (var playerUlid in cachedPlayerUlids)
{
    var playerData = LootLockerSDKManager.GetPlayerDataForPlayerWithUlid(playerUlid);
    
    // Replace with your own method to display player profile
    DisplayPlayerProfile(playerData.Name, playerData.ULID, playerData.CurrentPlatform.PlatformFriendlyString, playerData.LastSignIn);
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
TArray<FString> cachedPlayerUlids = ULootLockerSDKManager::GetCachedPlayerUlids();

for (const FString& playerUlid : cachedPlayerUlids)
{
    FLootLockerPlayerData playerData = ULootLockerSDKManager::GetSavedStateOrDefaultOrEmptyForPlayer(playerUlid);
    if (playerData.PlayerUlid.IsEmpty())
    {
        UE_LOG(<LogCategory>, Warning, TEXT("No player data found for ulid %s"), *playerUlid);
        continue;
    }

    // Replace with your own method to display player profile
    DisplayPlayerProfile(playerData.PlayerName, playerData.PlayerUlid, playerData.CurrentPlatform.GetFriendlyPlatformString(), playerData.LastSignIn);
}

// GetSavedStateOrDefaultOrEmptyForPlayer has the side effect of setting the player as active
// so it's a good idea to reset the active state after displaying profiles
ULootLockerSDKManager::SetAllPlayersToInactive();
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/5Dp1cSvCnmqcxY8317Xq" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/_ag33ssy/">Blueprint example of listing all previously signed in users</a></p></figcaption></figure>
{% endtab %}
{% endtabs %}

### Example: Select a profile (set as default)

When the player has selected which player to use for the current session you'll want to set this as the default user in LootLocker.

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

```cs
// Replace with actual player ULID
var selectedPlayerUlid = "selectedPlayerUlid";
if (!LootLockerSDKManager.SetDefaultPlayerUlid(selectedPlayerUlid))
{
    Debug.LogError("Failed to set default player ULID.");
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Replace with actual player ULID
FString selectedPlayerUlid = "selectedPlayerUlid";
if (!ULootLockerSDKManager::SetDefaultPlayer(selectedPlayerUlid))
{
    UE_LOG(<LogCategory>, Error, TEXT("Failed to set default player ULID."));
}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}

<figure><img src="/files/i2jgn6L4fgQrJ78M2apc" alt=""><figcaption><p>Blueprint example of setting the current default user</p></figcaption></figure>
{% endtab %}
{% endtabs %}


# Concurrent Players

LootLocker SDKs support managing multiple concurrent users in your game. This is useful for local multiplayer, hot seat, or any scenario where more than one player is authenticated at the same time.

### How it Works

Whenever you successfully authenticate a player (using any supported authentication method), the SDK automatically adds or updates that player's session in the multi user state. You do not need to manually add users. Just authenticate them as usual.

Each player is tracked by their ULID, and you can make API calls for a specific player by providing their ULID.

### Example: Start Concurrent Sessions and Submit Scores

This example is, of course, simplistic and abstracts away all the game logic that would go in between these two events. The example is here to show you how to make requests when there are multiple local users.

While we recommend you to always pass the ULID of the player you wish to make a request for, included in this example is a request to fetch the current scores that uses the *default player*, simply to show the difference between performing a request on the behalf of a specific player, or the default player.

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

```cs
// Replace with your actual leaderboard key
public static string leaderboardKey = "your_leaderboard_key_here";

public void OnLoginButtonPressed()
{
    // Replace with your actual player identifier retrieval logic
    string guest_player_identifier = GetGuestIdentifierFromInput();

    if (string.IsNullOrEmpty(guest_player_identifier))
    {
        Debug.LogWarning("Please enter a valid guest player identifier.");
        return;
    }

    LootLockerSDKManager.StartGuestSession(guest_player_identifier, response =>
    {
        if (!response.success)
        {
            Debug.LogError($"Login failed: {response.errorData}");
            return;
        }
        
        Debug.Log($"Login successful for guest player: {guest_player_identifier} with ulid {response.player_ulid}. The default player ulid is {LootLockerSDKManager.GetDefaultPlayerUlid()}");
    });
}

public void OnShowScoresButtonPressed()
{
    // Fetch the top 10 scores from the leaderboard, while we recommend always specifying which player
    // you want to make the request for, this is an example of making a request for the default player (simply not specifying a player).
    LootLockerSDKManager.GetScoreList(leaderboardKey, 10, 0, response =>
    {
        if (!response.success)
        {
            Debug.LogError($"Failed to retrieve scores: {response.errorData}");
            return;
        }
        Debug.Log($"Scores retrieved successfully: {response.text}");
        // Replace with your actual UI display logic
        DisplayScoresInUI(response.items);
    }/*, lootLockerPlayerUlid <- To make the request for a specific player you'd add this parameter*/);
}

public void OnMatchFinished_SubmitScores()
{
    // Replace with your actual game logic
    MatchData matchData = GetCurrentMatchData();
    foreach (var playerScore in matchData.PlayerScores)
    {
        // Submit each player's score to the leaderboard
        LootLockerSDKManager.SubmitScore("", playerScore.Score, leaderboardKey, response =>
        {
            if (!response.success)
            {
                Debug.LogError($"Failed to submit score for player {playerScore.PlayerId}: {response.errorData}");
            }
            else
            {
                Debug.Log($"Score submitted successfully for player {playerScore.PlayerId}: {response.text}");
            }
        }, /* IMPORTANT: */ playerScore.LootLockerPlayerULID /* This makes the score submission for *this* player */);
    }
}
```

{% endtab %}

{% tab title="Unreal C++" %}

```cpp
// Replace with your actual leaderboard key
FString leaderboardKey = "your_leaderboard_key_here";

void OnLoginButtonPressed()
{
	// Replace with your actual player identifier retrieval logic
	FString guest_player_identifier = GetGuestIdentifierFromInput();

	if (guest_player_identifier.IsEmpty())
	{
		UE_LOG(<LogCategory>, Warning, TEXT("Please enter a valid guest player identifier."));
		return;
	}

	ULootLockerSDKManager::GuestLogin(FLootLockerSessionResponse::CreateLambda([](const FLootLockerAuthenticationResponse& response) {
		if (!response.success)
		{
			UE_LOG(<LogCategory>, Error, TEXT("Login failed: %s"), *response.errorData);
			return;
		}

		UE_LOG(<LogCategory>, Log, TEXT("Login successful for guest player: %s with ulid %s. The default player ulid is %s"),
			*guest_player_identifier, *response.player_ulid, *ULootLockerSDKManager::GetDefaultPlayerUlid());
	}), guest_player_identifier);
}

void OnShowScoresButtonPressed()
{
	// Fetch the top 10 scores from the leaderboard, while we recommend always specifying which player
	// you want to make the request for, this is an example of making a request for the default player (simply not specifying a player).
	ULootLockerSDKManager::GetScoreList(leaderboardKey, 10, 0, FLootLockerGetScoreListResponseDelegate::CreateLambda([](const FLootLockerGetScoreListResponse& response)
	{
		if (!response.success)
		{
			UE_LOG(<LogCategory>, Error, TEXT("Failed to retrieve scores: %s"), *response.ErrorData.Message);
			return;
		}
		UE_LOG(<LogCategory>, Log, TEXT("Scores retrieved successfully: %s"), *response.text);
		// Replace with your actual UI display logic
		DisplayScoresInUI(response.items);
	})/*, lootLockerPlayerUlid <- To make the request for a specific player you'd add this parameter*/);
}

void OnMatchFinished_SubmitScores()
{
	// Replace with your actual game logic
	MatchData matchData = GetCurrentMatchData();
	for (const auto& playerScore : matchData.PlayerScores)
	{
		// Submit each player's score to the leaderboard
		ULootLockerSDKManager::SubmitScore("", leaderboardKey, playerScore.Score, FLootLockerSubmitScoreResponseDelegate::CreateLambda([playerScore](const FLootLockerSubmitScoreResponse& response)
		{
			if (!response.success)
			{
				UE_LOG(<LogCategory>, Error, TEXT("Failed to submit score for player %s: %s"), *playerScore.PlayerId, *response.ErrorData.Message);
			}
			else
			{
				UE_LOG(<LogCategory>, Log, TEXT("Score submitted successfully for player %s: %s"), *playerScore.PlayerId, *response.text);
			}
		}, /* IMPORTANT: */ playerScore.LootLockerPlayerULID /* This makes the score submission for *this* player */);
	}
}
```

{% endtab %}

{% tab title="Unreal Blueprints" %}
Starting sessions in a multi user context works the exact same as in a single user context. The only difference is that it becomes imperative to store the player ULID as that is what is used later to specify which player a request should be made on behalf of.

<figure><img src="/files/iDOyKaT5gN5oukxsohAR" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/5qvhkjth/">Blueprint example of starting guest sessions in a multi user context</a></p></figcaption></figure>

Below is an example of fetching the score list using the default user (not specifying which player ULID to execute the request as). While we recommend always specifying which user to perform the request as in a multi user game, this is *how* you use the default user.

<figure><img src="/files/D44Vsp5vK1xw6EaUrSud" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/5qvhkjth/">Blueprint example of getting the top 10 scores using the default player</a></p></figcaption></figure>

And finally, when the imaginary match finishes, submit the match scores for each player.

<figure><img src="/files/BV5DmcZ3eMVjsCR83YCR" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/5qvhkjth/">Blueprint example submitting user scores for each LootLocker player</a></p></figcaption></figure>
{% endtab %}
{% endtabs %}


# In a Server Context

In a server context, managing multiple users is different from a client game. The server typically [impersonates players or exchanges tokens](/server/unreal-server/how-to/unreal-server-impersonation) to obtain a Game API session token for each player, then uses the SDK to manage multi user state and make requests for many users in a secure context.

### How it Works

* The server obtains a session token for each player (using impersonation or token exchange).
* The SDK's `StartSessionManual` method is used to add each session to the multi user state.
* You can then make API calls for any player by specifying their ULID.
* Remember to use all the techniques from our guide on [how to end sessions and manage state](/players/multi-user/how-to/end-sessions-and-manage-state) so that you have the correct sessions active and ready in your server context.

For implementation details, refer to our [impersonation how to](/server/unreal-server/how-to/unreal-server-impersonation)


# Unified Player Accounts

Unified Player Accounts (UPA) is a powerful feature set that allows players to connect multiple authentication methods, enabling seamless cross-platform and cross-device experiences such as cross-progression and cross-saves. It also makes it easy for players to upgrade from a Guest account (or any other login type) to a White Label account with your game.

UPA simplifies the complex process of managing multiple logins by providing platform authentication solutions, enabling remote login across platforms, linking multiple identity providers, and offering player-facing websites for a smooth end-user experience.

### Supported Authentication Methods

Unified Player Accounts currently supports the following authentication methods:

* [Steam](/players/unified-player-accounts/how-to/configure-in-web-console/configure_steam_sign_in)
* [Epic Games](/players/unified-player-accounts/how-to/configure-in-web-console/configure_epic)
* [Sign in with Apple](/players/unified-player-accounts/how-to/configure-in-web-console/configure_apple_sign_in)
* [Sign in with Google](/players/unified-player-accounts/how-to/configure-in-web-console/configure_google_sign_in)
* [PlayStation](/players/unified-player-accounts/how-to/configure-in-web-console/configure_psn)
* [Xbox](/players/unified-player-accounts/how-to/configure-in-web-console/configure_xbox)
* [Discord](/players/unified-player-accounts/how-to/configure-in-web-console/configure_discord)

If you require support for additional identity providers, please contact us via [Discord](https://discord.gg/lootlocker) or [email](mailto:hello@lootlocker.com).

### Key Features

UPA consists of two core features: Remote Login and Connected Accounts, each designed to create a seamless and unified player experience.

#### Remote Login

Remote Login allows players to log in to a platform other than the one they are currently playing on. For example, a player on Nintendo Switch can log in to their Steam account to create a LootLocker session, even if they are not currently playing on Steam. This feature is ideal for enabling cross-platform interactions and ensuring players can access their accounts wherever they play.

#### Connected Accounts

Connected Accounts enables players to link multiple platform accounts under a single profile. For instance, a player can connect their Steam account to their Nintendo Switch account, creating a unified profile on the LootLocker backend. Once connected, the same profile will load regardless of whether the game is started on Steam or Switch.

When using connected accounts, you can choose to connect directly in the game client or redirect the player to the LootLocker authentication website to facilitate the authentication process.

[**External Linking**](/players/unified-player-accounts/how-to/external-link-to-player): This method redirects players to the LootLocker authentication website to authenticate with a provider used in the linking process. This is the recommended method for most use cases, as it provides a consistent experience across platforms and devices.

**Direct Linking**: This method is useful for flows where the player is currently playing on the platform you want to link with, for example when [transitioning from Guest Login to anther Provider](/players/unified-player-accounts/how-to/transition-guest-player).

#### Auth.game

As part of the UPA workflow, players are directed to our white-label authentication website, <https://auth.game>, to enter the 8-digit code provided by the game and verify their identity.

### Use Cases

* Account linking
* Cross-platform accounts
* Cross-saves
* Cross-progression
* Upsell connecting to White Label account while in-game

### How-to

* [Configure UPA in Web Console](/players/unified-player-accounts/how-to/configure-in-web-console)
* [Use Remote Login In-Game](/players/unified-player-accounts/how-to/remote-login-in-game)
* [Connect Identity Provider to Player](/players/unified-player-accounts/how-to/connect-provider-to-player)
* [Transition from Guest Login to Other Provider](/players/unified-player-accounts/how-to/transition-guest-player)
* [External Provider Linking](/players/unified-player-accounts/how-to/external-link-to-player)
* [Disconnect Identity Provider from a Player](/players/unified-player-accounts/how-to/disconnect-provider)

### Guides

* [Getting Started With Remote Login](https://lootlocker.com/guides/getting-started-with-remote-login)
* [Getting Started With Connected Accounts](https://lootlocker.com/guides/getting-started-with-connected-accounts)


# How To


# Configure In Web Console


# Apple

In this How To, we’ll go through all settings needed to setup Sign in with Apple for LootLockers Unified Player Accounts. This allows your users to authenticate with Sign in with Apple on <https://auth.game> to start remote sessions or link their accounts to other accounts.

### Prerequisities

* [A Game on LootLocker](/)
* [An existing Apple app with an App ID](/players/authentication/how-to/sign-in-with-apple)
* [An Apple Developer Account](https://developer.apple.com)

Go to your [Apple Developer Dashboard](https://developer.apple.com/account) and click on **Identifiers**.

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

Now, we will create a new **Service ID** to be used with LootLocker. Click the Magnifying glass next to **App IDs** in the top right corner and choose **Service IDs**:

<figure><img src="/files/2KiN4xcmAhMvwzZ1b5Ko" alt=""><figcaption></figcaption></figure>

Click on the + next to **Identifiers** to create a new Service ID:

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

Select **Services ID** and click Continue: Enter a description and an identifier for your **Services ID** and click Continue:

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

Verify your information and click **Register**:

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

Back in the list of **Services IDs**, click your newly created **Service ID**:

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

Enable **Sign in with Apple** by checking the checkbox and then click on \*\*Configure:

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

Select your already existing **App ID** from the dropdown list. In the field Domains & Subdomains, input

* `auth.game`
  * NOTE: without **https\://**
* In Return URLs, enter
  * `https://auth.game/return/apple`

Click **Next**

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

On the next screen, simply press **Done**:

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

{% hint style="warning" %}
Make sure to also click **Continue**, then **Save**.
{% endhint %}

{% hint style="info" %}
We’ve now configured a **Service**. Next up is to create a **Key** that is shared between LootLocker and Apple.
{% endhint %}

Click **All Identifiers** in the top left corner of the screen, and then select **Keys** in the list, or enter the URL <https://developer.apple.com/account/resources/authkeys/list> Click the `+`-icon to create a new **Key**

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

Enter a name for the **Key**, and a description (optional). Mark the checkbox for **Sign In With Apple** and press **Configure**.

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

Select your the same **App ID** as you have in your app from the dropdown list and click **Save**

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

We now get taken back to the previous screen, press **Continue**.

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

Now, click \*\*Register\*\*

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

Download the key and store it locally. Then press **Done**.

{% hint style="warning" %}
NOTE: Once the key has been downloaded, you can not access it again. Make sure that you don’t delete it or you will need to create a new key.
{% endhint %}

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

{% hint style="info" %}
Now, the full setup on Apple's side is complete and we can go to LootLocker to input our data.
{% endhint %}

First we will enable **External Authentication** and **Sign in with Apple** in the LootLocker [Web Console](https://console.lootlocker.com/settings/unified-player-accounts/apple)

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

With this enabled, we can now start to input the information that we have on our **Apple Developer Account**.

We start with **Client ID**. Got to the [Apple Developer Console](https://developer.apple.com/account/resources/identifiers/list) and copy the **Identifier** of your app:

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

And then paste it into the **Client ID** field in LootLocker:

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

Next, we have **Bundle ID** and **Team ID**. Click the entry for your app that had the **Client ID** that you copied and copy the **Team ID** and **Bundle ID**:

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

Paste them into LootLocker:

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

Now, press **Choose File** and upload the file that you downloaded previously from Apple:

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

### Conclusion

In this How To, we’ve enabled and configured Sign in with Apple for [Unified Player Accounts](https://github.com/lootlocker/gitbook-sync/blob/main/players/unified-player-accounts/README.md). With **Sign in with Apple** available in your game, you are fully prepared to start remote sessions with [Apple](https://apple.com) and by adding more platforms to Unified Player Accounts, you unlock the possibility for players to connect all their accounts together, creating a [Unified Player Account](/players/unified-player-accounts) for them to sync their [game saves](/players/files), [progressions](/game-systems/progressions) [and more](https://github.com/lootlocker/gitbook-sync/blob/main/the-basics/what-is-lootlocker.md).


# Google

![](/files/ZXnvrMIzslmfcA4GJkkS)

To learn more about the specific information needed for each field, you can read our general documentation on [Google Sign In](/players/authentication/how-to/sign-in-with-google)


# Steam

Steam requires no configuration for UPA, but for starting regular sessions you need a Steam App ID as well as a Partner Key.

To enable Unified Player Accounts for Steam, navigate to the **Settings** menu and select **Unified Player Accounts**. Select the Steam platform and click **Enable**.

![](/files/YrSzejxUMA60fu8VNKxy)


# Epic Games

![](/files/XXZ0fdQCZhJcIdLvcC7C)

To learn more about the specific information needed for each field, you can read our general documentation on [Epic Games Sign In](/players/authentication/how-to/sign-in-with-epic-games)


# PlayStation

If you want to use PlayStation as an identity provider, please reach out to us on Discord or on [email](mailto:hello@lootlocker.com?subject=PlayStation%Support).


# Xbox

If you want to use Xbox as an identity provider, please reach out to us on Discord or on [email](mailto:hello@lootlocker.com?subject=Xbox%Support).


# Discord

In this How To, we’ll go through all settings needed to setup Discord for LootLockers Unified Player Accounts. This allows your users to authenticate with Discord on <https://auth.game> to start remote sessions or link their accounts to other accounts.

### Prerequisities

* A LootLocker [Game and Account](/the-basics/what-is-lootlocker)
* [Discord set up for your game](/players/authentication/how-to/sign-in-with-discord)

After you have set up Discord for your game, we need to add a Redirect URL. Go to the [Discord Developer Portal](https://discord.com/developers/applications), and add a redirect uri to <https://auth.game/discord/return> in your oauth2 settings.

<figure><img src="/files/nJIhWUJASCDrzPNeuu8U" alt="Discord platform settings"><figcaption></figcaption></figure>

### Conclusion

In this How To, we’ve enabled and configured Discord for Unified Player Accounts. With Discord available in your game, you are fully prepared to start remote sessions with Discord (link) and by adding more platforms (link) to Unified Player Accounts, you unlock the possibility for players to connect all their accounts together, creating a Unified Player Account.


# Use Remote Login In-Game

Remote login makes it easy to start LootLocker sessions on devices where it's hard to enter their credentials, like on console. It also enables games to easily implement 1st party provider authentication without having to spend a long time manually implementing it into their game client.

## Pre-requisites

* [A game created in the LootLocker web console](/)
* [Game is configured to use UPA](/players/unified-player-accounts)

## The Remote Login Flow

The remote login flow consists of the following steps:

* Initiate the remote login process, which will return a URL and QR code the player can use to log in
* The player visits the URL and logs in using their preffered identity provider
* Game client polls the server to check if the player has logged in yet
* Once the player has logged in, the poll will return a success response with a LootLocker session and a refresh token
* The next time the player opens the game, the refresh token can be used to start a new session without the player having to go through the full flow again

## Starting a Remote Session

{% tabs %}
{% tab title="Unity" %}
As this function requires three separate callbacks, it is easier to manage them as following:

```csharp
public void RemoteLease()
{
    LootLockerSDKManager.StartRemoteSession(leaseAction, pollingAction, onComplete);
}

Action<LootLockerLeaseRemoteSessionResponse> leaseAction = (info) =>
{
    if (!info.success)
    {
        Debug.Log("Couldnt start Remote leasing");
        return;
    }
};
Action<LootLockerRemoteSessionStatusPollingResponse> pollingAction = (polling) =>
{
    if(!polling.success)
    {
        Debug.Log("Couldnt poll Remote leasing");
        return;
    }
};
Action<LootLockerStartRemoteSessionResponse> onComplete = (onComplete) =>
{
    if (!onComplete.success)
    {
        Debug.Log("Couldnt start Remote leasing");
        return;
    }
};
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/aoenqDSY6xZB6VsHgBIL" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/t40l99zu/">Blueprint for Starting a Remote Session</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}
First we call the endpoint to start the process

```bash
curl -X POST "https://api.lootlocker.io/game/session/remote/lease" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"game_version\": \"1.0.0.0\" }"
```

To Poll for updates we can use the same endpoint, but include `lease_code` and `nonce` in the body

```bash
curl -X POST "https://api.lootlocker.io/game/session/remote" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"game_version\": \"1.0.0.0\", \"lease_code\": \"R9SJMK2R\", \"nonce\": \"01HE2TM1JWRTPCWQFBYJ538XCE\" }"
```

{% endtab %}
{% endtabs %}

## Cancel the remote login process

If we for some reason want to cancel the process mid way, this is how we can do it.

{% tabs %}
{% tab title="Unity" %}
`LootLockerSDKManager.StartRemoteSession` returns a Guid which we can later use to cancel the remote session start process.

```csharp
public void RemoteLease()
{
    var leasedGuid =  LootLockerSDKManager.StartRemoteSession(leaseAction, pollingAction, onComplete);
    LootLockerSDKManager.CancelRemoteSessionProcess(leasedGuid);
}
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/x8Rfacak8fckbdEr5DLG" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/ibgd3_i8/">Blueprint on Cancelling a Remote Session Process</a></p></figcaption></figure>
{% endtab %}
{% endtabs %}

## Use the Refresh Token to Start a Session

In the response while creating our remote session we get a refresh token back. This token can be used to start a new session without the player having to go through the full flow again.

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

```csharp
LootLockerSDKManager.RefreshRemoteSession((onComplete) =>
{
    if(!onComplete.success)
    {
        Debug.Log("Couldnt refresh remote session!");
        return;
    }
});
```

With manually stored refresh token:

```csharp
string refreshToken = "";
LootLockerSDKManager.RefreshRemoteSession(refreshToken, (onComplete) =>
{
    if(!onComplete.success)
    {
        Debug.Log("Couldnt refresh remote session!");
        return;
    }
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/c5iIupnPmUGfKTghPSQe" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/5tb6ofw0/">Blueprint on Refreshing a Remote Session</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}

```bash
curl -X POST "https://api.lootlocker.io/game/session/remote" \
  -H "LL-Version: 2021-03-01" \
  -H "Content-Type: application/json" \
  -d "{\"game_key\": \"your_game_key\", \"game_version\": \"1.0.0.0\", \"refresh_token\": \"v4.public.eyJ.....\" }"
```

{% endtab %}
{% endtabs %}

## Conclusion

In this how-to we've shown how to start and manage remote sessions from your game. If you want to learn more about UPA you can read about [Connected Accounts](/players/unified-player-accounts/how-to/connect-provider-to-player) or the general [Unified Player Account](/players/unified-player-accounts) page.


# Connect Identiy Provider to Player

In this how-to we'll explain how to connect an additional identity provider to a player's account. This can be useful if the player wants to play from multiple devices, or if you want to offer the player the option to authenticate with different providers.

## Prerequisites

Before we begin, ensure you have the following:

* [A game created in the LootLocker web console](/)
* [UPA configured in the LootLocker web console](https://github.com/lootlocker/gitbook-sync/blob/main/players/unified-player-accounts/how-to/README.md)
* [A player with an active LootLocker game session](/players/authentication)

## Connect Provider

In order to connect an additional provider to the player, the general flow is as follows:

* Have the player signed in with an active LootLocker session on the game client
* Connect the provider to the player's LootLocker account, using the token from the authentication

Getting the authentication token is different for each provider, but the general idea is the same. In this example we'll use Apple sign in, but you can exchange this with another provider.

The following providers are currently supported:

* Apple Sign In
* Google Sign In

If you're interested in support for other identity providers, please [reach out to us!](https://lootlocker.com/contact)

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

```csharp
// The token retrieved from authenticating with Apple in the game client
string token = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0.xxxxx.xxxxxxxx-xxxxxxxxxxxxx";
LootLockerSDKManager.ConnectAppleAccount(token, (response) =>
{
    if(!response.success)
    {
        Debug.Log("Error connecting Apple");
        return;
    }

    Debug.Log("Successfully connected Apple");
});
```

{% endtab %}

{% tab title="Unreal" %}

<figure><img src="/files/DEqTe5EhOLwroIhFJcmw" alt=""><figcaption><p><a href="https://blueprintue.com/blueprint/i0rj-nc4/">Blueprint example of connecting Apple account</a></p></figcaption></figure>
{% endtab %}

{% tab title="REST" %}
Specific reference for providers are here:

* [Apple](https://ref.lootlocker.com/game/upa/connected-accounts/connect-apple-account)
* [Google](https://ref.lootlocker.com/game/upa/connected-accounts/connect-google-account)

The following example is for Apple:

```bash
curl -X PUT "https://api.lootlocker.io/game/v1/connected-accounts/apple-rest" \
    -H "x-session-token: your_token_here" \
    -H "Content-Type: application/json" \
    -d '{"authorization_code": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.0.xxxxx.xxxxxxxx-xxxxxxxxxxxxx"}'
```

{% endtab %}
{% endtabs %}

## Conclusion

In this how-to we've added an additional provider to a player's account, making it possible for them to authenticate with multiple different providers.

If you want to learn more, you can read about how to [disconnect a provider](/players/unified-player-accounts/how-to/disconnect-provider) or our general [Unified Player Account](/players/unified-player-accounts) documentation.




---

[Next Page](/llms-full.txt/1)

