Offline activation
Offline activation lets your app validate a license key locally, without calling the Lizen API. Useful for air-gapped environments, unreliable connections, or ultra-low latency requirements.
Available on Hobby tier and above.
How it works
- Your app calls
POST /v1/offline/generate(once, while online). Lizen returns a signed license file — an RS256 JWT — tied to the specific device fingerprint. - Your app stores the license file locally.
- On subsequent startups, your app verifies the JWT signature locally using the embedded public key. No network call needed.
- Every 90 days (or at the license’s
expires_at, whichever is sooner), the app must sync online to get a fresh license file and pick up any revocations.
Security model
- License files are RS256-signed JWTs. The private key lives in Cloudflare Worker Secrets and never leaves the server.
- Each license file is bound to a specific device fingerprint. It cannot be copied to another device.
- Revocations are picked up at the next sync. Maximum offline window before revocation takes effect: 90 days.
- Perpetual licenses (no
expires_at) still expire after 90 days offline — this forces phone-home to pick up revocations.
JWT payload
{ "iss": "https://api.lizen.dev", "sub": "lic_abc123", "iat": 1755648000, "exp": 1763424000, "kvl": { "plan": "pro", "deviceFingerprint": "sha256_of_device_id", "activationLimit": 3, "keyValue": "MYAPP-ABCD-EFGH-IJKL" }}Generate a license file
const license = await lizen.offline.generate({ key: 'MYAPP-ABCD-EFGH-IJKL', deviceFingerprint: getMachineId(), deviceName: "Alex's MacBook Pro", // optional});
// Save locallyawait fs.writeFile('license.jwt', license.licenseFile);Verify locally (no SDK needed)
import * as jose from 'jose'; // or any JWT library
const publicKey = await jose.importSPKI(embeddedPublicKey, 'RS256');const { payload } = await jose.jwtVerify(licenseFileContent, publicKey);
if (payload.kvl.deviceFingerprint !== getMachineId()) { // License file is for a different device throw new Error('Device mismatch');}Sync (extend the offline window)
Call POST /v1/offline/sync periodically to get a fresh license file with a new expiry. The device must have been previously activated.
const refreshed = await lizen.offline.sync({ key: 'MYAPP-ABCD-EFGH-IJKL', deviceFingerprint: getMachineId(),});
// Save updated license fileawait fs.writeFile('license.jwt', refreshed.licenseFile);Sync also picks up revocations — if the key has been revoked, sync returns a 422.