Install
The C# SDK licenses .NET apps, Godot 4 games, and Unity projects with online activation and offline Ed25519 lease verification. It targets netstandard2.0 and net8.0 with zero runtime dependencies, and verifies licenses identically to the Swift, Rust, and JavaScript SDKs (proven by cross-SDK conformance vectors).
Install
Section titled “Install”dotnet add package KeylightGodot 4 installs the same NuGet package through the editor’s .NET integration. Unity installs a separate UPM package — see Unity below.
Initialise
Section titled “Initialise”Fetch the tenant’s trusted keyset so leases verify offline, then build the client. The third argument to Builder is your tenant SDK key (sdk_live_…, from the Keylight dashboard) — it’s required and sent as the X-Keylight-SDK-Key header on every call.
using System.Linq;using System.Net.Http;using Keylight;
// Fetch the tenant's trusted Ed25519 keyset so leases can be verified offline.var http = new HttpClient();var keyset = await Keyset.FetchAsync(http, "https://api.keylight.dev", "your-tenant");
var config = KeylightConfig .Builder("your-tenant", "your-product", "sdk_live_…") .TrustedKeys(keyset?.Keys.ToDictionary(kv => kv.Key, kv => kv.Value) ?? new Dictionary<string, string>()) .MaxOfflineDays(15) // optional offline grace window (15 is the default) .AppVersion("1.4.2") // report app version on every call + beacon .Build();
var client = new KeylightClient(config);Activate and gate features
Section titled “Activate and gate features”// Activate a license key (online). The returned lease is Ed25519-verified// before anything is persisted.await client.ActivateAsync("USER-LICENSE-KEY");
// Gate features on entitlements — synchronous, from the cached lease.if (client.HasEntitlement("pro")){ // unlock pro features}
// Release the seat when uninstalling / switching devices.await client.DeactivateAsync();Call await client.CheckOnLaunchAsync() on startup: it refreshes the lease if it’s stale, auto-starts the trial clock on first launch, and reports the keyless beacon (or falls back to /config) for unlicensed installs.
Dashboard-owned settings
Section titled “Dashboard-owned settings”The Trial length and Free tier set in the dashboard are the values that apply; what you compile in is the seed a fresh install uses first. Read what is in force with client.EffectiveTrialDurationDays() and client.EffectiveFreeTierEnabled(); client.FetchConfigAsync() asks the server now instead of waiting for the next validate or beacon.
To verify that those settings really came from your dashboard, pin your key and turn on the check (0.4.0+):
var config = KeylightConfig .Builder("your-tenant", "your-product", "sdk_live_…") .TrustedKeys(new Dictionary<string, string> { { "k1", "<raw ed25519 public key, base64>" } }) .RequireSignedConfig(true) .Build();Leave it off until your app has a trial length set in the dashboard. That is what makes Keylight sign an app’s settings; with the switch on, an unsigned app rejects every legitimate response and stays on the value you compiled in. Once it is on, settings that do not verify against the keys you compiled in are never cached, on every route they arrive on. Pin the key in your build rather than fetching it: a keyset fetched over the same connection can be forged by the same attacker. See Verify the signature.
After a purchase
Section titled “After a purchase”Payment webhooks can lag the checkout redirect. RefreshAfterUpgradeAsync polls ValidateAsync briefly and returns true as soon as the entitlements or state change, false on timeout or when no license is stored:
if (await client.RefreshAfterUpgradeAsync()) // 30 s, polling every 2 s{ UnlockPaidFeatures();}A UPM (Unity Package Manager) package, dev.keylight.sdk, ships in the same repo under unity/dev.keylight.sdk and is kept byte-identical to the core library. Install it via Unity’s Package Manager using the Git URL, or via OpenUPM once published.
KeylightUnity.CreateClient(config) wires up a client that works on every Unity build target, including WebGL (where HttpClient is unavailable):
using Keylight;using Keylight.Unity;
var client = KeylightUnity.CreateClient( KeylightConfig.Builder("your-tenant", "your-product", "sdk_live_…") .TrustedKeys(new Dictionary<string, string> { ["k1"] = "hex-pubkey" }) .Platform(Application.platform.ToString()) .Build());
await KeylightUnity.CheckOnLaunchAsync(client);On Unity, use KeylightUnity.CheckOnLaunchAsync(client) rather than the client’s own CheckOnLaunchAsync(). It runs the same launch check — validate a stored license, start the trial clock, send the keyless beacon for an unlicensed install — and then stops the background keyless heartbeat, which Unity cannot run: UnityWebRequest works only on the main thread, and the heartbeat ticks on a background one. A Unity build reports its keyless state at launch, which is what puts trial and free-tier devices in your dashboard. To report again mid-session, call client.ReportKeylessStateAsync(...) from main-thread code.
It uses UnityWebRequestTransport instead of HttpClient, and feeds SystemInfo.deviceUniqueIdentifier into the SDK’s device identity so the cross-SDK machine_hash sent on activate, validate, and the keyless beacon has a stable source on every platform Unity targets. That id is Unity’s own cross-platform value, not the native id the other SDKs read (IOPlatformUUID on macOS, the registry MachineGuid on Windows) — it’s still stable across reinstalls, which is what device dedupe needs, but a Unity build and a native build of the same product installed on one machine hash differently.