Skip to content
Get Started

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.core is the required runtime package for authentication, sessions, analytics, and survey transport.
  • com.pragma.firstlook.ui is 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.

  1. Acquire the FirstLook Unity SDK from the admin dashboard. Download
SDK

  2. Set up your Steam game in FirstLook. See the Steam Integration.
  3. Invite a test player to your playtest with their Steam account linked. See the Inviting Players.

  4. Collect the values you’ll need for setup:

    • gameSlug from your FirstLook game settings
    • clientUrl from your FirstLook domain settings
    • Your Steam App ID

    To find your client URL, check your domain from your FirstLook settings: Client URL

Now that you’ve acquired the FirstLook Unity SDK and set up your game in FirstLook, install the packages into your Unity project.

  1. Unzip FirstLookUnitySdk.zip (the file you downloaded from the admin dashboard) to extract the com.pragma.firstlook.core and com.pragma.firstlook.ui package tarballs.

  2. Open Window > Package Manager in the Unity Editor.
  3. Select + > Add package from tarball... and install the com.pragma.firstlook.core tarball you extracted from the zip. This is the required package.

  4. Repeat the same Add package from tarball... flow for com.pragma.firstlook.ui if you want the packaged native in-game survey UI. If you plan to render survey UI yourself, you can skip this package.

  5. 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_STEAMWORKS when Steamworks.NET is installed, which enables FirstLookSteamHelper.

The Unity SDK reads configuration from a FirstLookSettings asset.

  1. Create Assets/Resources/FirstLookSettings.asset in the Unity Editor:

    • Assets > Create > FirstLook > Settings
  2. Configure the required fields on the asset:

    • apiUrl: https://api.firstlook.gg
    • clientUrl: https://<game-slug>.firstlook.gg
    • gameSlug: your FirstLook game slug
    • buildVersion: optional build label attached to analytics events (see Build versions)
  3. 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(...).

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
}
}

Now that the Unity SDK is installed and configured, test the Steam integration locally.

  1. Initialize Steamworks before your FirstLook startup code runs.

    • For example, add a scene object that calls SteamAPI.Init() in Awake() before any call to FirstLookSteamHelper.GetSessionTicket().

  2. Press Play in the Unity Editor or run a development build.
  3. In the Unity Console, confirm a successful startup log from your bootstrap code.

    • e.g. [FirstLookStartup] FirstLook ready. Session: 8c3b8f66-...
  4. If Steam setup is incomplete, expect one of these failure signals instead:

    • [SteamManager] SteamAPI.Init() failed...
    • [FirstLookStartup] Steam ticket unavailable...
    • [FirstLookStartup] FirstLook startup failed: ...

Once login and session startup are working, you can trigger in-game surveys from Unity.

  1. Create a survey with game client distribution enabled. See the Creating a Survey.

  2. Set up a trigger for your newly created survey. See the Setting Up Triggers.

  3. If you want the packaged native survey UI, install com.pragma.firstlook.ui and add a FirstLookSurveyPresenter with a FirstLookSurveyCanvas to your scene.

    • FirstLookSurveyPresenter subscribes to OnNewNativeSurveyAvailable, caches the active survey, and can reopen it through TryShowCurrentSurvey().

    • 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.

  4. If you want browser-delivered surveys, subscribe to OnNewBrowserSurveyAvailable in your host code and call ActivateSurveyBrowserOverlay(surveyUuid).

    • The shipped Unity UI package does not auto-open browser-delivered surveys for you.
  5. 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.

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.

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);
}

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.

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.

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");
}

Duration event names follow the same <category>.<event-type> convention as counter events. FirstLook automatically groups them by category prefix on the Analytics page.

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:

SourceHowBest for
Runtime overrideclient.OverrideBuildVersion("1.4.2")Reading the version from your own build pipeline at startup
AutomaticbuildVersionSource on FirstLookSettingsZero wiring: ApplicationVersion reads Player Settings, SteamAppId reads the running Steam App ID, which separates a Steam Playtest app from your main app
Settings fieldbuildVersion on FirstLookSettingsA 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.

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.

FieldContains
Player.IdThe FirstLook player ID
Player.StateThe player’s state as a lowercase string, such as unlinked or onboarded
Steam.SteamIdThe linked Steam ID. Steam is null when no Steam account is linked
Discord.DiscordIdThe linked Discord ID. Discord is null when no Discord account is linked
Role.Id, Role.NameThe player’s role

Check Player.IsUnlinked and Player.IsLinked rather than comparing State strings, since FirstLook can add states over time.

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.

To onboard unlinked players, first configure the From Game flow in the dashboard.

  1. Enable the Unlinked Claiming toggle on the SDK configuration page.
  2. Configure the Claim, Onboarding, and Complete steps of the From Game flow.
  3. Optional - configure the role.

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.

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.

StatusMeaning
403Linking is not enabled for this provider on your game, and result.Code is FeatureNotEnabled. Contact FirstLook to enable it.
409The ID is already linked to a different player in this game.
422The ID is empty or longer than 64 characters.

Failures are returned to you rather than queued for retry.

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.

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.

  1. On the SDK configuration page, turn on Enable Watermark under Playtest Watermark, then click Save Changes.
  2. 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.
  3. 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:

FieldDefaultRangeEffect
colorWhiteText color
opacity0.060 to 1Text opacity
tileDensity51 to 12Roughly how many codes fit across the screen width
angleDegrees300 to 90Tilt of the grid
driftSpeed0.50 to 20 holds the grid still
includeCentertrueKeeps 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.