All posts
Tutorial Unity 2021.2+ SDK

Integrate PermitCore with Your Unity Game in 15 Minutes

PC
PermitCore
September 17, 2026 · 11 min read
TaskManagerProDemo.cs — Unity 2022.3 // Awake() — never the constructor void Awake() { _client = new PermitCoreClient(ApiBaseUrl); } // Activate once, on first run var result = await _client.ActivateAsync(licenseKey); if (result.HasFeature("pro")) UnlockProFeatures(); // Package Manager → Built-in → enable "Unity Web Request" — see Step 2 below

PermitCore's Unity SDK is a zero-dependency client — no Newtonsoft.Json, no third-party package pulled in from the Unity Registry. It's also structured so its portable core (JSON, ECDSA offline verification, request building) can be tested with a plain dotnet test outside Unity entirely, which is how most of it was originally verified. This tutorial covers the other half: actually wiring it into a Unity project, including three real setup mistakes we hit ourselves the first time this SDK was opened in a genuine, licensed Unity Editor — not hypothetical gotchas, the exact errors we saw on screen.

Prerequisites

Unity 2021.2 or newer (the SDK targets .NET Standard 2.1, the default API Compatibility Level since that version), a PermitCore account, at least one product, and a generated license key.

Step 1: Install the SDK

The Unity SDK isn't on a UPM registry yet, so install it from the source ZIP:

  1. Download it from your PermitCore Admin panel → SDKs, or GET /api/sdks/unity.
  2. Extract it anywhere outside your project's Assets/ folder.
  3. In Unity: Window → Package Manager → + → Install package from disk... → select the extracted folder's package.json.

Step 2: The one compile error you're likely to hit

The first time we opened our own demo project in a real Unity Editor, it failed to compile with:

error CS1069: The type name 'UnityWebRequest' could not be found in the
namespace 'UnityEngine.Networking'. This type has been forwarded to assembly
'UnityEngine.UnityWebRequestModule' — not referenced by the compiling module.

Unity's built-in Unity Web Request module isn't enabled by default in every project template. The fix takes 30 seconds:

  1. Window → Package Manager
  2. Top-left dropdown → "Built-in" (not "Unity Registry" — built-in engine modules live in their own category)
  3. Search "Web Request", select Unity Web Request, click Enable

The SDK's own package.json now declares this as a dependency, so a fresh UPM install auto-enables it — you'll only hit this manually if you copy the Runtime/ files into a project directly instead of installing through Package Manager.

Step 3: Create the client — in Awake, not a field initializer

Attach a script to any GameObject and construct the client in Awake() or Start(). This isn't a style preference — Unity genuinely throws at runtime if you try to touch certain engine state from a constructor or a field initializer, since those run on the scene-loading thread, not guaranteed to be Unity's main thread:

using PermitCore;
using UnityEngine;

public class LicenseGate : MonoBehaviour
{
    public string ApiBaseUrl = "https://api.permitcore.dev";
    private PermitCoreClient _client;

    void Awake()
    {
        // Inside Unity, the transport arg is optional — defaults to UnityWebRequestTransport.
        _client = new PermitCoreClient(ApiBaseUrl);
    }
}

Step 4: Activate on first run

Call ActivateAsync once, the first time your game runs, and store the key locally so later launches only need to ValidateAsync. ActivateAsync handles the nonce round-trip and device fingerprinting for you:

private async System.Threading.Tasks.Task ActivateAsync(string licenseKey)
{
    try
    {
        var result = await _client.ActivateAsync(licenseKey);

        if (!result.IsValid)
        {
            ShowError(result.Message ?? "Could not activate this license key.");
            return;
        }

        PlayerPrefs.SetString("permitcore_license_key", licenseKey);
        if (result.HasFeature("pro")) UnlockProFeatures();
    }
    catch (System.Exception e)
    {
        // See "the bug this exact catch block would have caught" below.
        ShowError("Activation request failed: " + e.Message);
        Debug.LogError(e);
    }
}

Note the try/catch around the whole call. OnGUI/button-click handlers can't be async themselves, so you'll call this as fire-and-forget (_ = ActivateAsync(key);). If an exception happens inside a discarded Task that nothing ever awaits, it vanishes completely — no UI feedback, nothing in the Console. Skipping this catch is how our own demo's real bug (next section) went from "silent no-op" to "actually diagnosable" once we added it.

Step 5: The main-thread gotcha, if you ever touch the transport

This one bit us directly while first-testing this SDK's own demo in a real Editor, and it's worth knowing even if you never look inside UnityWebRequestTransport.cs: any code that eventually needs to call a Unity API (construct a new UnityWebRequest, touch a GameObject, anything engine-side) after an await must resume on Unity's main thread. Unity's own SynchronizationContext is what makes that work automatically — and .ConfigureAwait(false), which is completely standard, idiomatic practice in generic .NET libraries, explicitly opts out of exactly that mechanism.

If you fork the transport or chain your own awaits around SDK calls, avoid ConfigureAwait(false) anywhere the continuation needs to touch Unity APIs. Get this wrong and you'll see:

Create can only be called from the main thread.
Constructors and field initializers will be executed from the loading
thread when loading a scene. Don't use this function in the constructor
or field initializers, instead move initialization code to the Awake
or Start function.

That message is Unity's generic template for any main-thread violation, not literally about a constructor every time — in our case it fired from a chained await (a nonce fetch, then the activation POST), not a constructor at all. The shipped SDK's own PermitCoreClient.cs no longer uses ConfigureAwait(false) anywhere, specifically so this can't happen to you by default.

Step 6: Offline grace mode

Games ship to players who lose connectivity — a hotel Wi-Fi captive portal, a flight, a corporate firewall. Both ValidateAsync and ActivateAsync never throw and never hard-fail on a network error by themselves: if the license was previously activated with an offline grace period configured, the SDK transparently falls back to a signed local cache instead of blocking the player.

var result = await _client.ValidateAsync(storedKey);

if (result.IsOffline)
    ShowBanner("Offline — running on a cached license check.");

if (!result.IsValid)
{
    // Genuinely invalid/expired — not just unreachable
    BlockAccess(result.Message);
}

Error Handling Reference

LicenseResult never throws for an ordinary failure — IsValid, Message, and ErrorCode tell you what happened without exception handling in your gameplay code:

IsValid == trueKey is good — check IsOffline to see if it came from cache ErrorCode.ExpiredPast ExpiresAt, no grace period left ErrorCode.SeatsExhaustedMaxActivations reached for this key ErrorCode.VersionBlockedYour build's version is outside MinVersion/MaxVersion IsValid == false, IsOffline == trueServer unreachable and no valid cache — genuinely offline with nothing to fall back on

Testing Your Integration

Create a product in your PermitCore Admin and check "Test / Sandbox license" when generating a key — it works identically to a real one but is excluded from billing. Paste that key into your game's activation screen and confirm result.IsValid comes back true.

One thing worth checking explicitly, since PermitCore's /validate and /activate endpoints accept any license key belonging to your tenant regardless of which product it was issued for: if your game should only accept keys for its own product, pass the optional expectedProductId parameter (added in SDK 1.1.0 — this is exactly the gap our own demo's test run surfaced above) and let the server reject a mismatch for you (result.ErrorCode == "WrongProduct"), or compare result.ProductId yourself. Find your product's ID in the Admin panel under Products, or on a license's own detail page.

What's Next

This covers the core activate/validate flow. For feature-gated Pro tiers, floating/concurrent licenses, or fully offline signed-token verification (useful for a build with no network access at all), the Unity SDK reference covers each pattern, and the full SDK docs cover the same concepts across all 8 supported languages.

A complete, working reference — license gate, Free/Pro feature flags, CSV export, and an offline-token developer panel, all in one OnGUI script with zero scene/prefab setup required — is in the Task Manager Pro demo.

Previous: Announcing the Unity SDK All posts