Skip to Content
ElectronValidation and renewal

Validation and renewal

The SDK validates the key online on every launch, then again in the background before the token expires. Each validation that succeeds gives a new token, which replaces the stored one.

When the SDK validates

WhenWhy
On launch, when a key is storedChanges made on LicenseManager, like an expired license or a deactivated machine, apply right away when online. With a valid stored token, it runs in the background: see startup.
When the customer activates a key, with setLicenseKey()See activating a license.
At the renewal point, in the backgroundTo get a new token before the current one expires. See below.
While offline, on a backoffTo get back online as soon as possible. See retries.
When you call refresh()Like after the customer renewed their license.

Each validation counts as one API request toward your plan’s quota: with the default settings, a running app makes about one request per launch, plus one every 2/3 of the token’s lifetime. Retries only happen when LicenseManager can’t answer.

What’s sent

{ "key": "7KQ2M-X9ZPA-H4TRC-WN6EY-B3JDF", "fingerprint": "q3M0…:Xk9f…:x7Kp2QaZ", "version": "2.3.1", "name": "Janes-MacBook-Pro.local", "platform": "macOS" }
FieldFrom
keyThe stored key.
fingerprintSee machine fingerprint.
versionThe version option, or app.getVersion().
nameThe machineName option, or the hostname.
platformThe platform option, or macOS, Windows or Linux.

Each request times out after 10 seconds. Transient failures are retried.

What’s checked

The token in the answer is verified before it’s trusted, exactly like the offline validation does:

  • its signature, with your public key, accepting only EdDSA,
  • aud, equal to your productId,
  • sub, equal to the key,
  • fp, equal to this computer’s fingerprint,
  • exp and nbf, against the system clock,
  • iat and exp are present.

A token that fails is an invalid_response error, and is never stored.

How the answer is handled

AnswerDuring setLicenseKey()During any other validation
A valid tokenStored, ACTIVE.Stored, ACTIVE.
not_found, license_expired, version_not_allowed, machine_limit_reachedThrows. Nothing changes.The stored token is deleted, and the state becomes INVALID_KEY, EXPIRED, VERSION_NOT_ALLOWED or MACHINE_LIMIT_REACHED. No renewal is scheduled.
plan_limit_exceeded, validation_error, internal_error, an unknown errorThrows. Nothing changes.Falls back on the stored token: see working offline.
No answer, a timeout, an error answer that isn’t JSON, after the retriesThrows network_error. Nothing changes.Falls back on the stored token.
A token that fails verificationThrows invalid_response. Nothing changes.Falls back on the stored token.

Only the four locking codes can lock an app that was working. Whatever goes wrong with the network or with LicenseManager, a customer with a valid stored token keeps working.

Startup

On launch, with a stored key, the SDK first checks the stored token, like offline:

  • The stored token is valid: the state becomes ACTIVE and ready() resolves right away, without waiting for LicenseManager. The online validation runs in the background. When it refuses the license, the state changes, and your onStateChange listener locks the app.
  • No valid stored token, like in OFFLINE_EXPIRED, CLOCK_TAMPERED, or VERSION_NOT_ALLOWED from the token: ready() waits for the online validation, retries included, up to about 30 seconds.
main.ts
const state = await license.ready(); if (state === "ACTIVE") openMainWindow(); else openActivationWindow(); // The background validation may still lock the app. license.onStateChange((state) => { if (state !== "ACTIVE") lockApp(state); });

Retries

Each validation retries transient failures on its own, with an exponential backoff: each wait doubles, with some randomness, so that apps don’t all retry at the same time.

RetriedNot retried
No answer, a timeout, and the HTTP statuses 408, 429 and 5xx, like internal_error.Every other answer: the four locking codes, plan_limit_exceeded, validation_error, and a token that fails verification.

How long it retries depends on whether someone waits for the result:

ValidationFirst waitLongest waitGives up after
setLicenseKey(), refresh(), and startup without a valid stored token0.5 second5 seconds30 seconds
In the background: startup with a valid stored token, renewals, and retries while offline2 seconds1 minute5 minutes

A request never runs past the end of that time: the last one times out early if needed. Once it’s over, the validation fails with the last error: setLicenseKey() throws it, the other validations fall back on the stored token.

These retries happen within one validation. When it still fails, the SDK schedules the next one later, from 5 minutes to 1 hour: see working offline.

When the key is validated again

After each successful validation, the SDK schedules the next one at 2/3 of the token’s lifetime. With a 1 week token, the next validation is 4 days and 16 hours later: the customer can go offline right before it, and still has 2 days and 8 hours left.

The token’s lifetime is the license’s max offline duration, shortened when the license expires sooner.

minOfflineDurationMs

If your customers must be able to stay offline for a given time whenever they go offline, set minOfflineDurationMs: the SDK renews earlier, so that at least that much validity remains after the renewal point.

new LicenseManager({ productId: "x7Kp2QaZ", publicKey: PUBLIC_KEY, minOfflineDurationMs: 3 * 24 * 60 * 60 * 1000, // 3 days });

The renewal happens at the earliest of:

  • 2/3 of the token’s lifetime,
  • the token’s expiration minus minOfflineDurationMs, but never less than 1 day after the token was issued.

The 1 day floor stops a minOfflineDurationMs close to or longer than the token’s lifetime from validating the key again and again. It only limits minOfflineDurationMs: tokens shorter than 1 day are still renewed at 2/3 of their lifetime.

Token lifetimeminOfflineDurationMsRenewed afterLeft when renewed
1 week0 (default)4 days 16 hours2 days 8 hours
1 week3 days4 days3 days
1 week1 week1 day (the floor)6 days
4 weeks2 weeks2 weeks2 weeks
1 hour040 minutes20 minutes
1 hour1 day40 minutes20 minutes

minOfflineDurationMs can’t give more offline time than the token’s lifetime: the max offline duration of the license is the limit. To let customers stay offline longer, raise the max offline duration of the policy.

Near the license’s expiration

A token never outlives its license: a license expiring in 2 days gives tokens that expire in 2 days. Each renewal happens at 2/3 of a shorter and shorter lifetime, so the SDK validates more often in the last day, a handful of requests in total. At the expiration, LicenseManager answers license_expired, and the state becomes EXPIRED. Offline at that moment, the token expires and the state becomes OFFLINE_EXPIRED.

When you extend the license before it expires, the next renewal gets a token with the new date.

Timers

The renewal uses a timer that doesn’t keep your app running: quitting isn’t delayed by it. Long delays are split into steps of at most 1 hour, each checking the system clock: after the computer sleeps, or when its clock changes, the validation runs at most about 1 hour late, not days late. A 3 month token is renewed after 2 months as expected.

To validate as soon as the computer wakes, call refresh() from Electron’s powerMonitor:

main.ts
import { powerMonitor } from "electron"; powerMonitor.on("resume", () => void license.refresh());

Each call is one API request: don’t do it on events that happen often.

refresh()

refresh() validates the stored key now, and resolves with the new state.

ipcMain.handle("license:refresh", () => license.refresh());
  • It waits for ready() first, then validates, with the short retries: it resolves within about 30 seconds.
  • It never throws: the result is in the state, handled like any background validation. A network error falls back on the stored token.
  • Without a stored key, it does nothing, and resolves with MISSING_KEY.
  • On success, the next renewal is rescheduled from the new token.
  • A call while another refresh() or setLicenseKey() of the same key is running shares its request. A background validation running at the same time doesn’t hold it up: refresh() sends its own request.

Use it for the buttons of locked screens, I renewed my license or Try again, and after events where the license may have changed.

Edge cases

The app is updated to a new version. The first launch of the new version validates with its version: when the license doesn’t include it, the state becomes VERSION_NOT_ALLOWED, with the latest version the license allows.

A long-running app. An app left open for weeks is validated by the renewals only, not on launch. Changes on LicenseManager reach it at the next renewal, at most 2/3 of the token’s lifetime later.

A license refused since the last launch. The stored token is still valid, so ready() resolves with ACTIVE, and the customer sees your app until the background validation comes back, usually under a second. The state then becomes EXPIRED, or another refused state: lock the app from your onStateChange listener.

Startup without a valid stored token, on a slow network. ready() waits for the validation, up to about 30 seconds with the retries. Show a splash screen meanwhile.

After dispose(). No renewal is scheduled anymore, pending retries stop, and the clock check stops. A request already running still completes, and can still change the state and call the listeners. refresh() and setLicenseKey() still work, but schedule nothing.