Skip to main content
Introducing packages.sweber.dev
Documentation menuLicense keys

License keys

The license format, what it contains and how verification works.

An Integral license is a string like int1.eyJ2IjoxLC….Qx8f…: a prefix, the license data as base64url JSON, and an Ed25519 signature over both. It fits in an environment variable, a license file or a text field.

Contents

FieldMeaning
idUnique id, generated if you do not pass one. Stays the same when you re-issue a license.
productYour product. verifyLicense({ product }) rejects licenses for other products.
planPlan name from your plan definitions.
features, limitsExtras on top of the plan.
seatsSeats, if you sell per person or machine.
customerid, email, name. Optional; it is readable by anyone who has the license.
iat, nbfIssued at, not valid before.
expHard expiry: the license stops working.
updatesUntilEnd of updates: newer versions are not covered, older ones keep working. See Update periods.
kidKey id for key rotation.
metaSmall free-form data, e.g. an order id.

Licenses are signed, not encrypted. Do not put secrets in them.

Verification

const result = await verifyLicense(license, {
  publicKey: [CURRENT_KEY, PREVIOUS_KEY], // rotation: several keys are accepted
  product: "my-app",
  clockTolerance: 300, // seconds, for nbf and exp
});

Verification is offline and takes well under a millisecond. To show license details without trusting them, use decodeLicense(license), which does not check the signature.

Key rotation

  1. Create a new key pair and ship an app version that accepts both public keys.
  2. Sign new licenses with the new key and set kid.
  3. Once old licenses are replaced or no longer matter, remove the old public key.

Where to keep the private key

On your server or in your CI secrets, never in the app. If it leaks, anyone can issue licenses: create a new key pair, re-issue all licenses and ship an app version that only accepts the new public key.