Unity SDK Setup
The FirstLook Unity SDK lets you interface with FirstLook from your Unity game. The SDK supports Steam and Google Play authentication, in-game surveys, session tracking, custom events, custom duration events, player lookup, PlayFab identity linking, entitlements, and playtest watermarking.
The SDK ships as two Unity packages:
com.pragma.firstlook.coreis the required runtime package for authentication, sessions, analytics, and survey transport.com.pragma.firstlook.uiis an optional native survey UI package built on top of core.
The main integration surface is FirstLookClient, which you initialize through FirstLookBootstrap.InitializeWithSettings(...). If you install the UI package, FirstLookSurveyPresenter is the main packaged entry point for native in-game survey rendering.
For this setup guide, we will assume you have a Unity project named MyGame.
Prerequisites
Section titled “Prerequisites”Acquire the FirstLook Unity SDK from the admin dashboard.

- Set up your Steam game in FirstLook. See the Steam Integration.
Invite a test player to your playtest with their Steam account linked. See the Inviting Players.
Collect the values you’ll need for setup:
gameSlugfrom your FirstLook game settingsclientUrlfrom your FirstLook domain settings- Your Steam App ID
To find your client URL, check your domain from your FirstLook settings:

Acquire the FirstLook Unity SDK from the admin dashboard.

Set up Google Play in FirstLook. See the Google Play Integration guide for how to configure your closed testing track, create the required OAuth 2.0 credentials, and add your client ID to FirstLook.
Specifically, you need a Web application OAuth 2.0 client ID (not an Android client ID) added to Config > Google Play in your FirstLook dashboard. You will also need an Android OAuth client registered with your app’s package name and SHA-1 fingerprint so Google accepts sign-in requests from your build.
Collect the values you’ll need for setup:
gameSlugfrom your FirstLook game settingsclientUrlfrom your FirstLook domain settings
Installing the FirstLook Unity packages
Section titled “Installing the FirstLook Unity packages”Now that you’ve acquired the FirstLook Unity SDK and set up your game in FirstLook, install the packages into your Unity project.
Unzip
FirstLookUnitySdk.zip(the file you downloaded from the admin dashboard) to extract thecom.pragma.firstlook.coreandcom.pragma.firstlook.uipackage tarballs.- Open
Window > Package Managerin the Unity Editor. Select
+ > Add package from tarball...and install thecom.pragma.firstlook.coretarball you extracted from the zip. This is the required package.Repeat the same
Add package from tarball...flow forcom.pragma.firstlook.uiif you want the packaged native in-game survey UI. If you plan to render survey UI yourself, you can skip this package.Add your platform authentication dependency:
Add Steamworks.NET to
Packages/manifest.json.{"dependencies": {"com.rlabrecque.steamworks.net": "https://github.com/rlabrecque/Steamworks.NET.git?path=/com.rlabrecque.steamworks.net#2025.163.0"}}Unity automatically defines
FIRSTLOOK_STEAMWORKSwhen Steamworks.NET is installed, which enablesFirstLookSteamHelper.The FirstLook SDK accepts a raw Google ID token, so you can use any Google Sign-In library that provides one — for example, the Google Play Games Plugin for Unity or the Google Sign-In Unity Plugin.
Install your preferred Google Sign-In library and configure it with the Web application client ID you added to your FirstLook dashboard. This must be the same client ID — it is used to both initiate sign-in and verify the resulting ID token.
Once the user signs in, retrieve the ID token from the library and pass it to
AuthenticateWithGoogle(idToken). The exact API varies by library:- Google Play Games Plugin: configure
RequestIdToken = trueinPlayGamesClientConfiguration, then retrieve the token with((PlayGamesLocalUser)Social.localUser).GetIdToken()after sign-in. - Google Sign-In Unity Plugin: after
GoogleSignIn.DefaultInstance.SignIn()resolves, readGoogleSignInUser.IdTokenfrom the result.
Refer to your chosen library’s documentation for the full sign-in setup.
- Google Play Games Plugin: configure
Configuring FirstLookSettings
Section titled “Configuring FirstLookSettings”The Unity SDK reads configuration from a FirstLookSettings asset.
Create
Assets/Resources/FirstLookSettings.assetin the Unity Editor:Assets > Create > FirstLook > Settings
Configure the required fields on the asset:
apiUrl:https://api.firstlook.ggclientUrl:https://<game-slug>.firstlook.gggameSlug: your FirstLook game slugbuildVersion: optional build label attached to analytics events (see Build versions)
Leave the retry, queue, and logging settings at their defaults unless your project needs different behavior.
FirstLookSurveyPresenter and FirstLookAutoLifecycle load settings with FirstLookSettings.LoadFromResources() when none is assigned. It reads Resources/FirstLookSettings, and in the Editor and development builds prefers Resources/FirstLookSettingsLocal when it exists. Use the Local asset to point local builds at a test game without changing the asset you ship.
clientUrl is only required if you plan to open browser-delivered surveys through ActivateSurveyBrowserOverlay(...).
Authentication bootstrap
Section titled “Authentication bootstrap”The preferred Unity integration path is host-owned bootstrap. Initialize FirstLook once, authenticate with your platform, and start the session explicitly.
Make sure Steamworks has already been initialized, then authenticate and start the session.
using Pragma.FirstLook;using UnityEngine;
public sealed class FirstLookStartup : MonoBehaviour{ [SerializeField] private FirstLookSettings firstLookSettings;
private FirstLookClient _client;
private async void Start() { var settings = firstLookSettings != null ? firstLookSettings : Resources.Load<FirstLookSettings>("FirstLookSettings");
if (settings == null) { Debug.LogError("[FirstLookStartup] Missing FirstLookSettings."); return; }
_client = FirstLookBootstrap.InitializeWithSettings(settings);
#if FIRSTLOOK_STEAMWORKS var ticket = FirstLookSteamHelper.GetSessionTicket(); if (string.IsNullOrWhiteSpace(ticket)) { Debug.LogError("[FirstLookStartup] Steam ticket unavailable. Make sure Steamworks is initialized first."); return; }
var result = await _client.AuthenticateAndStartSessionAsync( FirstLookAuthRequest.Steam(ticket, FirstLookSteamHelper.GetAppId()));
if (!result.Success) { Debug.LogError($"[FirstLookStartup] FirstLook startup failed: {result.AuthResult?.Error}"); return; }
Debug.Log($"[FirstLookStartup] FirstLook ready. Session: {result.SessionId}");#else Debug.LogError("[FirstLookStartup] Steamworks.NET is not installed.");#endif }}Use your Google Sign-In library to obtain a Google ID token, then pass it to FirstLook.
using Pragma.FirstLook;using UnityEngine;
public sealed class FirstLookStartup : MonoBehaviour{ [SerializeField] private FirstLookSettings firstLookSettings;
private FirstLookClient _client;
// Call this once you have obtained a Google ID token from your // Google Sign-In library (e.g. Google Play Games Plugin or Google Sign-In Unity Plugin). public async void AuthenticateWithGoogle(string googleIdToken) { var settings = firstLookSettings != null ? firstLookSettings : Resources.Load<FirstLookSettings>("FirstLookSettings");
if (settings == null) { Debug.LogError("[FirstLookStartup] Missing FirstLookSettings."); return; }
_client = FirstLookBootstrap.InitializeWithSettings(settings);
if (string.IsNullOrWhiteSpace(googleIdToken)) { Debug.LogError("[FirstLookStartup] Google ID token was empty."); return; }
var result = await _client.AuthenticateAndStartSessionAsync( FirstLookAuthRequest.GoogleIdToken(googleIdToken));
if (!result.Success) { Debug.LogError($"[FirstLookStartup] FirstLook startup failed: {result.AuthResult?.Error}"); return; }
Debug.Log($"[FirstLookStartup] FirstLook ready. Session: {result.SessionId}"); }}Testing login
Section titled “Testing login”Now that the Unity SDK is installed and configured, test the Steam integration locally.
Initialize Steamworks before your FirstLook startup code runs.
For example, add a scene object that calls
SteamAPI.Init()inAwake()before any call toFirstLookSteamHelper.GetSessionTicket().
- Press Play in the Unity Editor or run a development build.
In the Unity Console, confirm a successful startup log from your bootstrap code.
- e.g.
[FirstLookStartup] FirstLook ready. Session: 8c3b8f66-...
- e.g.
If Steam setup is incomplete, expect one of these failure signals instead:
[SteamManager] SteamAPI.Init() failed...[FirstLookStartup] Steam ticket unavailable...[FirstLookStartup] FirstLook startup failed: ...
Test the Google Play integration on a device or emulator with Google Play Services.
Initialize your Google Sign-In library and trigger the sign-in flow to obtain a Google ID token.
Pass the token to
AuthenticateWithGoogle(idToken)in your bootstrap code.In the Unity Console, confirm a successful startup log.
- e.g.
[FirstLookStartup] FirstLook ready. Session: 8c3b8f66-...
- e.g.
If authentication fails, expect one of these signals:
[FirstLookStartup] Google ID token was empty.[FirstLookStartup] FirstLook startup failed: Invalid Google ID token: ...
Testing out In-Game Surveys
Section titled “Testing out In-Game Surveys”Once login and session startup are working, you can trigger in-game surveys from Unity.
Create a survey with game client distribution enabled. See the Creating a Survey.
Set up a trigger for your newly created survey. See the Setting Up Triggers.
If you want the packaged native survey UI, install
com.pragma.firstlook.uiand add aFirstLookSurveyPresenterwith aFirstLookSurveyCanvasto your scene.FirstLookSurveyPresentersubscribes toOnNewNativeSurveyAvailable, caches the active survey, and can reopen it throughTryShowCurrentSurvey().Start from the packaged survey prefabs, skins, and theme assets that ship with the UI package, then duplicate them into your project if you want to customize them.
If you want browser-delivered surveys, subscribe to
OnNewBrowserSurveyAvailablein your host code and callActivateSurveyBrowserOverlay(surveyUuid).- The shipped Unity UI package does not auto-open browser-delivered surveys for you.
Trigger the survey from your game:
await _client.PostSurveyTriggerEventAsync("match.completed");
If you are building fully custom survey UI, you can skip com.pragma.firstlook.ui entirely and subscribe to the core survey events on FirstLookClient directly.
Sessions and Events
Section titled “Sessions and Events”Session Tracking
Section titled “Session Tracking”The Unity SDK does not start sessions just by being installed. A session begins when your host calls StartSessionAsync() or uses AuthenticateAndStartSessionAsync(...) successfully.
Once started, the SDK tracks the active session until you end it or the app shuts down. Internally, FirstLookRuntimeDriver attempts to call EndSessionAsync() on application quit as a best-effort graceful shutdown. Players who haven’t created a FirstLook account yet are still tracked — they’ll appear as Unlinked players in your dashboard, with their platform account associated.
Once sessions are being recorded, you’ll see player activity and session duration data on the Analytics page under the Retention tab, as well as on each individual Player Profile.
Custom Events
Section titled “Custom Events”Beyond session tracking, you can instrument your own gameplay events to capture whatever matters most to your studio — hero selections, match outcomes, store purchases, and more. Use PostCounterEventAsync(...) on FirstLookClient to send a named counter with an integer value:
public async void HandleHeroSelected(){ if (_client == null || !_client.IsSessionActive) return;
await _client.PostCounterEventAsync("loadout.hero-a-selected", 1);}Naming and Grouping Events
Section titled “Naming and Grouping Events”Event names follow a <category>.<event-type> convention. When you use this pattern, FirstLook will automatically group related events together on the Analytics page and on Player Profiles, making it easy to browse metrics by category.
Choose a category namespace that reflects where the event occurs in your game — for example, match, lobby, menu, store, or in-game. You’re free to define as many categories as you need.
Parameters
Section titled “Parameters”Pass an optional parameter to split one event into variants, such as the level a player started or the item they bought, without defining a new event name for each:
public async void HandleLevelStarted(string levelId){ if (_client == null || !_client.IsSessionActive) return;
await _client.PostCounterEventAsync("level.started", levelId, 1);}FirstLook stores the variant as level.started#forest_3, charts it under the base event on the Analytics page, and sums every variant into the base total. Parameters are trimmed and lowercased, may be at most 64 characters, and must not contain #. A blank parameter sends a plain event, and the two-argument overload keeps working unchanged. Keep the set of values small, because each distinct parameter is tracked as its own variant.
Custom Duration Events
Section titled “Custom Duration Events”Counter events record that something happened, but sometimes you need to know how long something took. Duration events let you measure elapsed time between two explicit points in your game — a match, a loading screen, time spent in a menu, or any other span you want to track.
Call StartDurationEventAsync(...) when the activity begins and EndDurationEventAsync(...) when it finishes. The SDK handles timing and delivery — you just provide a name.
public async void OnMatchStart(){ if (_client == null || !_client.IsSessionActive) return;
await _client.StartDurationEventAsync("match.round");}
public async void OnMatchEnd(){ if (_client == null || !_client.IsSessionActive) return;
await _client.EndDurationEventAsync("match.round");}Naming and Grouping Events
Section titled “Naming and Grouping Events”Duration event names follow the same <category>.<event-type> convention as counter events. FirstLook automatically groups them by category prefix on the Analytics page.
Build Versions
Section titled “Build Versions”Session, counter, and duration events can each carry a build_version. FirstLook treats it as an opaque label and stores whatever you send, so the format is entirely your studio’s choice: a semver like 1.4.2, a CI build number, a branch-plus-commit string. Pick one convention and keep it stable, since the build filters on player filters match exact values.
There are three ways to supply it, in order of precedence:
| Source | How | Best for |
|---|---|---|
| Runtime override | client.OverrideBuildVersion("1.4.2") | Reading the version from your own build pipeline at startup |
| Automatic | buildVersionSource on FirstLookSettings | Zero wiring: ApplicationVersion reads Player Settings, SteamAppId reads the running Steam App ID, which separates a Steam Playtest app from your main app |
| Settings field | buildVersion on FirstLookSettings | A label you set by hand per build |
buildVersionSource defaults to Manual, which uses the buildVersion field as-is. A source that cannot resolve, such as SteamAppId without Steamworks.NET installed, falls back to the buildVersion field.
Players
Section titled “Players”Current Player
Section titled “Current Player”Call GetCurrentPlayerAsync() to read the authenticated player’s FirstLook record, for example to decide whether to show a claim prompt:
public async void RefreshPlayer(){ var me = await _client.GetCurrentPlayerAsync(); if (me == null) return;
if (me.Player.IsUnlinked) await _client.GetPlayerClaimAsync();}The call returns null when the client is not authenticated or the request fails.
| Field | Contains |
|---|---|
Player.Id | The FirstLook player ID |
Player.State | The player’s state as a lowercase string, such as unlinked or onboarded |
Steam.SteamId | The linked Steam ID. Steam is null when no Steam account is linked |
Discord.DiscordId | The linked Discord ID. Discord is null when no Discord account is linked |
Role.Id, Role.Name | The player’s role |
Check Player.IsUnlinked and Player.IsLinked rather than comparing State strings, since FirstLook can add states over time.
Onboard Unlinked Players
Section titled “Onboard Unlinked Players”The SDK creates new players in the Unlinked state. You can give these players a link that lets them claim their account and onboard to complete their profile, unlocking emails, rewards, Discord, friend invites, and everything else their role provides.
From Game Flow
Section titled “From Game Flow”To onboard unlinked players, first configure the From Game flow in the dashboard.
- Enable the Unlinked Claiming toggle on the SDK configuration page.
- Configure the Claim, Onboarding, and Complete steps of the From Game flow.
- Optional - configure the role.
Provide Claim Link
Section titled “Provide Claim Link”After the From Game flow is set up, your game client can call GetPlayerClaimAsync() on FirstLookClient and subscribe to its OnPlayerClaimAvailable event. When the event is triggered, it provides a URL and a Base64-encoded QR code. Present this link or QR code to the player to allow them to complete email verification and any onboarding steps configured in the From Game flow.
Linking External Identities
Section titled “Linking External Identities”Once the player is authenticated, you can tell FirstLook the player’s ID on an external service so FirstLook can match the two records. PlayFab is currently the only supported provider.
var result = await _client.LinkIdentityAsync(FirstLookIdentityProvider.Playfab, playfabId);if (!result.Success){ Debug.LogWarning($"PlayFab link failed (status={result.StatusCode}): {result.Error}");}Relinking the player’s own ID succeeds, so it is safe to call on every startup.
| Status | Meaning |
|---|---|
403 | Linking is not enabled for this provider on your game, and result.Code is FeatureNotEnabled. Contact FirstLook to enable it. |
409 | The ID is already linked to a different player in this game. |
422 | The ID is empty or longer than 64 characters. |
Failures are returned to you rather than queued for retry.
Entitlements
Section titled “Entitlements”Entitlements are in-game items you grant to players from FirstLook. The SDK delivers entitlements of the FirstLook type: create one under Entitlements in the dashboard, then grant it to players, for example with the Grant Entitlement bulk action. Your game fetches a player’s outstanding entitlements, grants the items, and redeems each one so it is not delivered again. Pragma entitlements are delivered through Pragma instead and don’t appear here. If Entitlements doesn’t appear in your dashboard, contact FirstLook to enable it.
public async void SyncEntitlements(){ var result = await _client.GetEntitlementsAsync(); if (!result.Success) { Debug.LogWarning($"Entitlements unavailable (status={result.StatusCode}): {result.Error}"); return; }
foreach (var entitlement in result.Entitlements) { // Key the grant on entitlement.Id so a retried redeem grants the item once. MyInventory.GrantOnce(entitlement.Id, entitlement.Sku);
var redeem = await _client.RedeemEntitlementAsync(entitlement.Id); if (!redeem.Success && redeem.Error != "Entitlement already redeemed") Debug.LogWarning($"Redeem {entitlement.Id} failed (status={redeem.StatusCode}): {redeem.Error}"); }}Each FirstLookEntitlement carries Id, EntitlementType, Name, Sku, RedeemableTypeId, and CreatedAt. Match items on Sku, the Entitlement ID you set in the dashboard, and never on Name, which is a display string that can be translated or renamed.
Redeeming is one-shot. Redeeming the same entitlement again fails with 400 and Entitlement already redeemed, which usually means an earlier attempt succeeded, so treat it as done. Neither call is queued for retry: a failure comes back in Success, StatusCode, and Error.
Watermark Overlay
Section titled “Watermark Overlay”The SDK can watermark your playtest builds. Each session gets a unique code, and the SDK tiles it across the screen at an angle, drifting slowly, with the center always covered so cropping can’t remove it. If a screenshot or stream leaks, the code traces it back to the player and session it came from.
- On the SDK configuration page, turn on Enable Watermark under Playtest Watermark, then click Save Changes.
- After a session starts, the SDK fetches that session’s code and draws the overlay automatically. When the session ends the overlay is removed, and the next session shows its own code.
- To trace a leak, enter the code on the Search watermark page to find the player and session it belongs to.
FirstLook controls only whether the watermark is on. How it looks is set in your project: the overlay is a screen-space canvas above all other UI that persists across scene loads and does not block input. Tune it in the Watermark section of your FirstLookSettings asset:
| Field | Default | Range | Effect |
|---|---|---|---|
color | White | Text color | |
opacity | 0.06 | 0 to 1 | Text opacity |
tileDensity | 5 | 1 to 12 | Roughly how many codes fit across the screen width |
angleDegrees | 30 | 0 to 90 | Tilt of the grid |
driftSpeed | 0.5 | 0 to 2 | 0 holds the grid still |
includeCenter | true | Keeps the screen center covered |
Subscribe to OnWatermarkEnabled to learn when the overlay appears; it carries the code. The overlay renders itself, so use it only to tell players their session is watermarked:
_client.OnWatermarkEnabled += code => ShowToast("This playtest session is watermarked.");If watermarking is not enabled for your game, the game runs without an overlay and the SDK logs a warning: Watermarking is not enabled for this game. Contact FirstLook to enable it. Set logLevel on FirstLookSettings to Warning or lower to see it.