Unity SDK
Unity SDK
Unity 2021.2+ client (.NET Standard 2.1 API Compatibility Level), zero external package dependencies — no Newtonsoft.Json, no additional UPM packages. Works on Desktop, Mobile, and Console builds; WebGL works for online calls, with one crypto caveat noted below.
Installation
Listed on OpenUPM
(com.permitcore.sdk) — the simplest option if you have the
openupm-cli installed:
openupm add com.permitcore.sdk
Or install directly from the tagged git repo — Unity Package Manager's git-URL install works with no manual setup: Window → Package Manager → + → Install package from git URL...
https://github.com/permitCore-spec/permitcore-sdk-unity.git#v1.1.0
Or work from the source ZIP above: extract it and in Unity:
Window → Package Manager → + → Install package from
disk... and select the extracted folder's package.json. Or copy
Runtime/'s contents straight into your project's Assets/ folder.
Quick start — validate
using PermitCore;
using UnityEngine;
public class LicenseGate : MonoBehaviour
{
private PermitCoreClient _client;
async void Start()
{
_client = new PermitCoreClient("https://api.permitcore.dev");
var result = await _client.ValidateAsync(
"PERMIT-XXXX-XXXX-XXXX-XXXX",
expectedProductId: "7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b"); // optional — reject a key issued for a different product
if (result.ErrorCode == "WrongProduct")
{
Debug.LogError("This key is for a different product: " + result.ProductId);
return;
}
if (!result.IsValid)
{
Debug.LogError("License invalid: " + result.Message);
return;
}
if (result.HasFeature("pro"))
EnableProFeatures();
}
}
Activate (call once per installation)
var result = await _client.ActivateAsync(
"PERMIT-XXXX-XXXX-XXXX-XXXX",
deviceId: null, // auto-generated when omitted
deviceName: SystemInfo.deviceName,
version: Application.version, // optional — enforces min/maxVersion
expectedProductId: "7f3e9a2b-1c4d-4e8f-9a6b-3d2c1e0f5a8b"); // optional
if (result.ErrorCode == "WrongProduct")
Debug.LogError("Wrong product: " + result.ProductId);
else if (!result.IsValid)
Debug.LogError("Activation failed: " + result.Message);
Metered billing
await _client.MeterAsync(licenseKey, "level_completed");
await _client.MeterAsync(licenseKey, "export", quantity: 5,
meta: new Dictionary<string, object> { ["format"] = "png" });
Floating licenses
var session = await _client.CheckoutAsync(licenseKey);
if (!session.Success) { Debug.LogError("No seats available: " + session.Message); return; }
// Heartbeat every 4-5 minutes (e.g. from a coroutine or InvokeRepeating)
await _client.HeartbeatAsync(session.SessionToken);
// On quit / logout
await _client.CheckinAsync(session.SessionToken);
Offline license tokens
No extra package needed — System.Security.Cryptography.ECDsa is part of
Unity's .NET Standard 2.1 surface on Desktop, Mobile, and Console targets.
// Pure local verification — no network call, never throws on Desktop/Mobile/Console.
var result = PermitCoreClient.VerifyOfflineToken(token, publicKeyBase64);
if (result.IsValid)
Debug.Log("Valid! Product: " + result.Payload.ProductName);
// Bind to this device + persist locally (call once, e.g. at install time)
var activated = _client.ActivateOffline(token, publicKeyBase64, deviceId);
// On every later launch — no token needed, reads the local cache
var cached = _client.ValidateOffline(deviceId);
System.Security.Cryptography implementation, so the
three offline-token methods above will throw on that one platform. Online
ValidateAsync/ActivateAsync work fine on WebGL via
UnityWebRequest — only the fully-offline crypto path is affected, and
that's a platform constraint of WebGL itself, not something this SDK can work around.
Hardware ID
// Default: a random id persisted to a local file — works identically on every platform.
string hwid = _client.GetHardwareId();
// Unity-idiomatic alternative, recommended on mobile/console:
string hwid = UnityHardwareId.Get(); // SHA-256 of SystemInfo.deviceUniqueIdentifier
LicenseResult reference
| Field | Type | Description |
|---|---|---|
IsValid | bool | True if the license is active and valid |
ProductName | string | Product the license belongs to |
RemainingActivations | int? | Activation slots remaining |
ExpiresAt | string | ISO 8601 expiry date, null = perpetual |
Features | List<string> | Feature flag list, e.g. ["export","api"] |
IsTrial | bool | True for trial licenses |
TrialDaysRemaining | int? | Days until trial expires |
NodeLocked | bool | True if bound to a specific device |
OfflineGraceDays | int? | How many days the cache remains valid |
MinVersion / MaxVersion | string | Version enforcement bounds |
VendorWarning | string | Non-fatal message from the vendor |
Message | string | Reason when IsValid == false |
IsOffline | bool | True when result came from local cache |
ErrorCode | string | Stable, machine-readable failure reason |
ProductId | string | Stable ID of the product this license belongs to. Always present when a license was found, even without passing expectedProductId. |
HasFeature(string feature) — case-insensitive feature check.
Product scoping: by default, any active key belonging to your tenant validates/activates successfully regardless of which product it was issued for — this is exactly the gap this SDK's own demo surfaced while testing it in a real Unity Editor. Both ValidateAsync/ActivateAsync take an optional trailing expectedProductId parameter — set it and the server rejects a mismatch with ErrorCode == "WrongProduct". Find a product's ID in the Admin panel under Products.