Skip to main content

License a desktop app

Written for developers

This is a step-by-step technical guide to adding a license screen to your product: it has code to copy and API calls to make. If you sell the product but don't write its code, read Not a developer? Start here and send this page to your developer.

Desktop apps are licensed per computer. The customer pastes their key on a license screen, the app activates that machine, and it re-checks in the background. Pick the section for your stack.

Licensr does not host your installer. Host it on your own website and link your buy page from there.

Product settings​

SettingValue
Activation modeSeat: one activation per computer.
Client typeNative app. Desktop apps send no browser Origin. (Electron: the code below runs in the main process, which also sends none.)
IdentifierA stable ID for the computer, see each section.

Create a plan​

In the admin panel, open your product and add a plan: one price with a limit on how many devices or websites one key can be used on. Keep the defaults if you are unsure. You can add more plans later. See the Quick start.

Also create a Client API key on the product (shown once, starts with pk_test_ or pk_live_). This is the key you put in your product.

Get a test license​

You do not have to pay to try your integration. In the admin panel go to Customers → Issue license, enter your own email, pick the product and plan, and issue it. The key (starts with lic_) arrives by email and is shown once on screen. The dashboard checklist has a shortcut for this.

Use your pk_test_ key against https://api.staging.licensr.app while you build, then switch to pk_live_ and https://api.licensr.app when you release.

What your license screen must do​

Your product needs one small screen: a text box for the key, an Activate button, and a Deactivate button. It follows these rules, whatever the platform:

  1. Activate, then validate. When the customer clicks Activate, call activate (this registers the device or website against the plan's limit), then validate. Unlock only if the validate response has valid: true. Save the key and the activation_id from the activate response.
  2. Check again in the background. Call validate at every launch and about once a day while the product runs. A refund, a cancelled subscription, or an expired license happens after the sale, and this daily check is how your product finds out. Without it, a refunded key keeps working forever.
  3. Lock immediately on valid: false. If Licensr cannot be reached, stay unlocked (optionally lock after a few days with no answer). Try again later.
  4. Offer a Deactivate button. It calls deactivate with the saved activation_id, forgets the key, and locks the product. This lets a customer move to a new computer without asking you for help.

Messages to show when activate fails (read detail.error in the response):

CodeSay
license_not_found"We could not find that key. Check for typos."
activation_cap_exceeded"This key is already in use on the maximum number of devices. Deactivate it on another device first."
license_inactive"This license is no longer active. Contact the seller."
no response at all"Could not reach the license server. Try again."
any other code"Something went wrong (code)." Show the code so the customer can send it to you.

The full list is in Errors.

Electron and Node​

Use the JavaScript SDK in Electron's main process, not in the renderer. The renderer is a web page, so it would send a browser Origin and expose your key to page scripts.

npm install @licensr/sdk node-machine-id
import {LicensrClient, LicensrApiError} from '@licensr/sdk';
import {machineIdSync} from 'node-machine-id';
import Store from 'electron-store';
import os from 'node:os';

const store = new Store<{key?: string; activationId?: string}>();
const deviceId = machineIdSync(); // stable per computer

const client = new LicensrClient({
apiKey: 'pk_live_...', // Client key, safe to ship in the app
pluginSlug: 'my-app',
deviceId,
});

// Customer clicked "Activate".
export async function activate(licenseKey: string): Promise<string | null> {
try {
const activation = await client.activate({
licenseKey,
activationType: 'seat',
identifier: deviceId,
label: os.hostname(),
});
const check = await client.validate({licenseKey});
if (!check.valid) return 'This license is not active.';
store.set({key: licenseKey, activationId: activation.activationId});
return null;
} catch (err) {
if (err instanceof LicensrApiError && err.code === 'activation_cap_exceeded') {
return 'This key is already used on the maximum number of computers.';
}
return 'We could not activate that key. Check for typos and your connection.';
}
}

// At launch, and again every 24 hours.
export async function checkInBackground(onInvalid: () => void) {
const licenseKey = store.get('key');
if (!licenseKey) return;
try {
const check = await client.validate({licenseKey});
if (!check.valid) onInvalid();
} catch {
// offline or server trouble: keep the app unlocked
}
}
setInterval(() => checkInBackground(lockApp), 24 * 60 * 60 * 1000);

// Customer clicked "Deactivate".
export async function deactivate() {
const licenseKey = store.get('key');
const activationId = store.get('activationId');
if (licenseKey && activationId) await client.deactivate({licenseKey, activationId}).catch(() => {});
store.clear();
lockApp();
}

Expose activate and deactivate to the renderer through ipcMain.handle and a preload script, so the license screen in your window can call them.

.NET (WPF, WinForms, MAUI)​

Use the C# SDK: dotnet add package Licensr.Sdk. There is no built-in machine ID, so create a GUID once and save it with your app settings.

using Licensr.Sdk;

var client = new LicensrClient(new LicensrClientConfig
{
ApiKey = "pk_live_...", // Client key, safe to ship in the app
PluginSlug = "my-app",
DeviceId = deviceId, // your saved GUID
});

// Customer clicked "Activate".
var activation = await client.ActivateAsync(new ActivateRequest
{
LicenseKey = licenseKey,
ActivationType = ActivationType.Seat,
Identifier = deviceId,
Label = Environment.MachineName,
});
var check = await client.ValidateAsync(new ValidateRequest { LicenseKey = licenseKey });
if (check.Valid) SaveLicense(licenseKey, activation.ActivationId);

// Every launch and daily: only an explicit Valid == false locks.
// Any exception (offline, rate limited, server error) keeps the previous state.
try
{
var daily = await client.ValidateAsync(new ValidateRequest { LicenseKey = licenseKey });
if (!daily.Valid) LockApp();
}
catch (Exception) { /* keep the previous state */ }

// Customer clicked "Deactivate":
await client.DeactivateAsync(new DeactivateRequest { LicenseKey = licenseKey, ActivationId = savedActivationId });

The SDK throws LicensrApiException (check .Code) when Licensr answers with an error and LicensrNetworkException when it cannot be reached.

Python, Swift, Rust, Go and others​

There is no SDK, but the API is three plain HTTPS calls. Follow the any-language guide and use the operating system's machine ID as the identifier (IOPlatformUUID on macOS, MachineGuid on Windows, /etc/machine-id on Linux), hashed together with your product slug.

Licensr does not host your download. Host the file on your own website or store, then add a Buy button that links to your Licensr buy page (the dashboard shows the full link, which looks like /buy/your-slug). Customers pay there and get their key by email, then paste it into your license screen.

Go live​

  1. Swap your pk_test_ key for the pk_live_ key and use https://api.licensr.app in the release build.
  2. Buy your own product once, using the buy page, to see the whole flow.
  3. Check the dashboard: the Get started checklist shows You're live! when payments, a product, a plan, a test license, and a first check from your product are all done.

Stuck? The license calls guide lists every field and error.