License a Unity Game Without a Backend: UPM, IL2CPP, Launch Config
Most Unity games sold direct ship with the same license check: a key format regex, a string compare, maybe a request to a PHP script on shared hosting. It works on launch day. Then one buyer runs the same key on ten machines, a refund goes through and the key keeps working forever, and you spend a weekend writing the device tracking you told yourself you would never need. That is the real cost of hand-rolled unity game licensing: a second product you maintain next to the game.
This post is the other path. Install the Keylight package over UPM, build IL2CPP and WebGL safe, read your trial length from the dashboard when the game starts, and gate content from a MonoBehaviour. No server of your own. About an afternoon.
What the hand-rolled key check costs you
A key check with no server behind it cannot count devices, so one key becomes a family license for a Discord. A key check with a server behind it is now infrastructure you host, patch, and keep online during launch week. Neither version knows about refunds. Your payment tool processes the chargeback, and the license keeps working because nothing ever told the game.
Keylight is the layer between the payment and the game. Connect the payment tool you already use, Stripe for example, set a price, and the key is minted and emailed the moment someone buys. A refund or chargeback revokes it and frees its seats. Device limits are a number in the dashboard, not a table you design. And the game verifies its license locally, so it plays on a flight.
Install over UPM from the git URL
The package is dev.keylight.sdk, and it targets Unity 2021.3 and newer. Add it from the Package Manager with Add package from git URL:
https://github.com/keylight-dev/keylight-csharp.git?path=unity/dev.keylight.sdk
Or add the line to Packages/manifest.json directly:
{
"dependencies": {
"dev.keylight.sdk": "https://github.com/keylight-dev/keylight-csharp.git?path=unity/dev.keylight.sdk"
}
}
That is the whole install. No .unitypackage to import, no DLLs to drop into Plugins/, no NuGet resolver to fight. The Unity package mirrors the core C# SDK source, so the version in the package matches the NuGet release. Today that is 0.4.1.
Why zero NuGet references matters for IL2CPP and WebGL
The package has no NuGet references at all. The signature check that verifies your license is written in pure managed C#. There is no DllImport, no native .so or .dylib, and no System.Security.Cryptography call that behaves differently from one runtime to the next.
For IL2CPP that is the difference between a build that works and a build that fails at runtime with a stripped method. IL2CPP compiles ahead of time. Anything that relies on reflection, dynamic code, or a platform crypto provider is a gamble. Pure managed code is not. The verification result is checked in CI against the same cross-SDK conformance vectors the Swift, Rust, JavaScript, and C++ SDKs pin, and every release goes through a manual IL2CPP and WebGL build checklist before it ships. I will not call that a device-farm proof. It is the honest version: the math has no platform-specific branch to get wrong, and the builds are exercised before each tag.
For WebGL there is one more detail. The .NET HttpClient does not exist in the WASM runtime because there are no threading primitives to back it. The Unity package ships a UnityWebRequest transport and wires it in automatically when you create the client through KeylightUnity.CreateClient. You do not configure anything. The same script builds for standalone, mobile, consoles, and the browser.
Read the trial length from the dashboard at launch
This is the part I want Unity developers to stop hardcoding. Your trial length is a dashboard setting. So is the free-tier flag. When the game starts with no active license, CheckOnLaunchAsync fetches your product config from the server, and from then on EffectiveTrialDurationDays() returns the dashboard value. You can change 14 days to 7 during a sale and every install picks it up on its next launch. No rebuild, no store review, no patch notes.
The resolution order is simple. Server value first. If the server has never been reached, the seed you compiled in. If neither, zero, which means no trial. A 0 from the dashboard is a real setting, not a missing one.
The C# SDK has no keyless beacon yet, so this launch fetch is the one route that reaches a trial player. It runs only when there is no active license. A paying player never pays for that round trip. The free-tier flag comes back on the same call. The SDK reports the flag, and what free actually unlocks stays your decision, because there is no dedicated free-tier state in C# today. Read EffectiveFreeTierEnabled(), then gate whatever you want free.
Here is what it looks like inside a MonoBehaviour:
using System.Collections.Generic;
using Keylight;
using Keylight.Unity;
using UnityEngine;
public class LicenseGate : MonoBehaviour
{
[SerializeField] GameObject proContent;
[SerializeField] GameObject trialBanner;
KeylightClient _client;
async void Start()
{
// Public key from your dashboard keyset, pinned at build time.
var trustedKeys = new Dictionary<string, string> { ["k1"] = "<public-key>" };
_client = KeylightUnity.CreateClient(KeylightConfig
.Builder("your-tenant", "your-game", "sdk-key")
.TrustedKeys(trustedKeys)
.Platform(Application.platform.ToString())
.Build());
// Verifies a cached lease locally, refreshes if due,
// and reads the dashboard config when there is no license.
await _client.CheckOnLaunchAsync();
int trialDays = _client.EffectiveTrialDurationDays();
bool freeTier = _client.EffectiveFreeTierEnabled();
trialBanner.SetActive(_client.State == KeylightState.Trial);
Debug.Log($"trial: {trialDays} days, free tier: {freeTier}");
}
// Called from your key-entry UI.
public async void OnPlayerEntersKey(string key)
{
try
{
await _client.ActivateAsync(key);
}
catch (ActivationException e)
{
Debug.LogWarning(e.Message);
}
}
void Update()
{
// Sync, reads the verified lease, no network. Safe on the main thread.
proContent.SetActive(_client.HasEntitlement("pro"));
}
}
Four states come back from State: Licensed, Trial, Expired, and Invalid. HasEntitlement reads from the verified lease in memory and never touches the network, so it is fine to call every frame if you want to. Define the entitlement labels in the dashboard, and the lease carries exactly the ones a purchase grants.
Activate once, verify offline, catch revocations later
Activation binds the key to the machine and stores a signed lease on disk. Every launch after that verifies the lease locally against the public key you pinned. If the signature checks out and the lease is current, the game loads with no request. If Keylight is unreachable for a week, the player notices nothing.
That offline run is bounded. The Unity package defaults to 15 days without a successful online check, the same as the core SDK. Set it lower if your game is always-online anyway, or set it to zero to trust the lease for as long as the lease itself is valid. The clock rollback trick, where a player sets the system date back to stretch a trial, is caught.
Revocation is the honest part. When a refund lands, the key is revoked on the server immediately, but a device holding a valid lease keeps playing until it next checks in. That happens on the next launch through CheckOnLaunchAsync, or mid-session through ActiveRevalidateAsync, which you can call from a menu or a save point. It debounces itself to once a minute so you cannot spam the server by accident. I will never promise an instant device kill, because no licensing system that works offline can deliver one.
What Alpha means here
The C# SDK carries an Alpha badge on the docs site, and I would rather say that in the post than have you find it on the package page.
Stable today, and covered by the same conformance vectors as the other four SDKs: activation, validation, offline lease verification, the dashboard-owned trial length and free-tier flag, the opt-in signed config check with pinned keys, and RefreshAfterUpgradeAsync for picking up a tier change after a purchase. That is the full loop a shipped game needs.
Not there yet: the keyless beacon that lets the dashboard count free players who never entered a key, and the dedicated FreeTier and Limited states that the Swift, Rust, and JavaScript SDKs already resolve. In Unity today, free-tier players are a flag you read and a gate you write, not a device the dashboard sees. Those land in a later release. Watch the changelog in the keylight-csharp repository, and pin the package to a tag if you want the git URL to stop moving.
Where to go from here
If you are on Unreal instead, the C++ plugin path is in License Your Unreal Engine Game Offline in an Afternoon. If your game has a companion tool or launcher in WPF or MAUI, the same NuGet package covers it, and License Keys for .NET Apps: Desktop and Server with Keylight walks through that side. The trial and free-tier settings you read above are described on the Free Trials and Free Tier page, and the SDK matrix with version badges lives in the docs.
Pricing starts free, so you can wire the whole thing up before you have a single buyer, which is when licensing should be set up anyway.
If you hit something this post does not cover, send your feedback and I will extend it.
Nicolas Demanez, Founder
Frequently asked
Does Keylight work with IL2CPP builds?+
Yes. The package is pure managed C# with no native libraries and no DllImport, so IL2CPP ahead-of-time compilation has nothing to strip or break. Lease verification is checked against the same cross-SDK conformance vectors in CI, and every release goes through a manual IL2CPP and WebGL build checklist.
Can I change the trial length without shipping a new build?+
Yes. The trial length and the free-tier flag live in your Keylight dashboard. On launch, a game with no active license reads them from the server, so a change in the dashboard reaches every install on its next start.
Does the game still run without internet?+
Yes. After activation the game holds a signed lease it verifies locally, bounded by an offline window of 15 days by default. A revoked or refunded key is caught at the next launch or on the next mid-session revalidation, not instantly.
Is the Unity package production ready?+
The C# SDK carries an Alpha badge on the docs site. Activation, validation, offline verification, the dashboard-owned trial length, and the free-tier flag are stable today. The keyless beacon and the dedicated FreeTier state that the other SDKs have are not in C# yet.
Ready to ship?
Create your account and start licensing your apps in under a minute. Free forever tier included.
Start Free