Skip to Content
ElectronLicense states

License states

At any time, the SDK sums up the license in one state. Your app only needs it to decide what to show: the app unlocked, the activation screen, or why it’s locked.

license.getState(); // "ACTIVE" const unsubscribe = license.onStateChange((state) => { for (const window of BrowserWindow.getAllWindows()) { window.webContents.send("license:state", state); } });

The states

StateUnlock the app?Meaning
LOADINGWaitThe SDK is checking the stored key on startup.
MISSING_KEYNoNo key is stored yet.
ACTIVEYesThe license is valid: validated online, or with a stored token that hasn’t expired.
INVALID_KEYNoLicenseManager answered not_found: the key doesn’t exist in this product.
EXPIREDNoLicenseManager answered license_expired.
VERSION_NOT_ALLOWEDNoLicenseManager answered version_not_allowed, or, offline, the stored token doesn’t include this version.
MACHINE_LIMIT_REACHEDNoLicenseManager answered machine_limit_reached.
OFFLINE_EXPIREDNoLicenseManager can’t be reached, and there’s no valid stored token.
CLOCK_TAMPEREDNoLicenseManager can’t be reached, and the system clock was moved.

ACTIVE is the only state that unlocks your app. Whether it’s ACTIVE online or offline, see getOfflineState() in working offline.

LOADING

The state on construction, while the stored key is checked. With a valid stored token, it’s short: the SDK becomes ACTIVE once the clock check is done, usually under a second, up to 5 seconds offline, and validates online in the background. Without one, it lasts as long as the online validation: usually under a second, up to about 30 seconds on a network that doesn’t answer, with the retries. See startup. The SDK never goes back to LOADING afterwards.

Show a splash screen, or nothing, until ready() resolves.

MISSING_KEY

No key is stored: the app was never activated on this computer, or its stored data was deleted. Show the activation screen. The SDK makes no request in this state.

ACTIVE

The license is valid. Either the last validation succeeded, or LicenseManager couldn’t be reached and the stored token is valid: its signature, product, key and fingerprint match, it hasn’t expired, and its mv includes this version.

The SDK renews the token in the background. Nothing to do.

INVALID_KEY

The stored key doesn’t exist in this product anymore. Usually, you deleted the license, or the app was built for another product than the one the key belongs to, like a test key in a release build.

Show the activation screen, with: “Your license key is no longer valid. Enter a new one.”

EXPIRED

The license’s expiration date has passed. Offer to renew, with a link to your pricing page or the customer portal, and a I renewed my license button that calls refresh(): when you extend the license, the same key works again, and the customer has nothing to enter.

VERSION_NOT_ALLOWED

The license doesn’t include this version of your app, usually because its updates ended before it was released. getLatestAllowedVersion() returns:

ValueWhenWhat to show
A version, like "2.3.1"LicenseManager answered with the latest version the license allows.“Your license includes Acme Photo up to 2.3.1.” Offer to download it, or to upgrade the license.
nullLicenseManager answered: the license allows none of your releases.Offer to upgrade the license.
undefinedThe refusal comes from the stored token, offline: it only knows the license’s max version, not your latest release.“Your license doesn’t include this version. Connect to the internet to check for the versions it includes.”

See versions offline.

MACHINE_LIMIT_REACHED

The license is active on as many machines as it allows, and this one isn’t one of them. It happens on a new computer, or after this computer was deactivated and the license’s places were taken in the meantime.

Show how to free a machine, with a link to the customer portal, and a Try again button that calls refresh().

OFFLINE_EXPIRED

LicenseManager can’t be reached, and there’s no stored token, or it has expired, or it fails verification. The customer stayed offline longer than the license’s max offline duration, or the token was lost.

Ask the customer to connect to the internet. The SDK retries on its own, every 5 minutes at first, then up to every hour, and becomes ACTIVE as soon as it succeeds. A Try again button calling refresh() saves them the wait.

CLOCK_TAMPERED

LicenseManager can’t be reached, and the clock check found the system clock was set back, or is more than 5 minutes off. The stored token isn’t trusted, even if it’s still valid.

Ask the customer to set their date and time automatically, and to connect to the internet. The SDK retries like for OFFLINE_EXPIRED.

Locked app window explaining the license expired, with Renew and I renewed my license buttons

How states change

The states fall into three groups:

GroupStatesDoes the SDK retry on its own?
UnlockedACTIVEYes: it renews the token before it expires.
Refused by LicenseManagerINVALID_KEY, EXPIRED, VERSION_NOT_ALLOWED, MACHINE_LIMIT_REACHEDNo. The stored token is deleted, and nothing is scheduled. The SDK validates again on refresh(), setLicenseKey(), or the next launch.
OfflineOFFLINE_EXPIRED, CLOCK_TAMPERED, and VERSION_NOT_ALLOWED when it comes from the stored tokenYes: with a backoff from 5 minutes to 1 hour, until LicenseManager answers.

A refused license stays refused for the whole session: if you extend an expired license, the app notices it at its next launch, or when the customer clicks a button calling refresh(). That’s why the locked screens should always have one.

Listening to changes

onStateChange(listener) calls listener with the new state each time it changes, and returns a function that removes the listener.

  • It’s not called with the current state when you subscribe: read getState() for that.
  • It’s only called when the state changes: a renewal that keeps the app ACTIVE doesn’t call it.
  • It’s called synchronously, in the order listeners were added.
  • An error thrown by a listener is swallowed: it doesn’t break the SDK, or stop the other listeners.

ready() returns the same promise every time, resolved with the state on startup: ACTIVE right away with a valid stored token, otherwise once the startup validation is done. It never rejects. Since the validation of a valid stored token runs after it, always keep a listener to lock the app.

Linking to the customer portal

getPortalUrl() returns the link to your product’s customer portal, like https://app.licensemanager.dev/portal/x7Kp2QaZ, where customers renew their license and free their machines. Open it in the customer’s browser from your locked screens:

main.ts
import { ipcMain, shell } from "electron"; ipcMain.handle("license:open-portal", () => shell.openExternal(license.getPortalUrl()));

To bring the customer back to your website after, add a return_url: see allowed return domains.

Edge cases

A refused license, launched offline. The stored token was deleted when the license was refused. Launched offline, the SDK can’t ask LicenseManager, and has no token to fall back on: the state is OFFLINE_EXPIRED, not EXPIRED. It becomes EXPIRED again once online.

An INVALID_KEY key stays stored. The SDK validates it again on each launch, and gets INVALID_KEY again, until the customer enters another key.

A machine deactivated from the dashboard. The app doesn’t notice until its next validation. At that validation, LicenseManager activates the machine again if the license has a free place, and the app stays ACTIVE. When the license is full, it answers machine_limit_reached, and the state becomes MACHINE_LIMIT_REACHED.

A license extended while the app runs EXPIRED. Nothing happens until refresh() or the next launch.

The organization is over its plan (plan_limit_exceeded), or LicenseManager answers 500. Handled like being offline: the app keeps running on its stored token, and the customer notices nothing while it’s valid.