1. First activation
The user enters a key, the client reports a device id, and the server claims a slot atomically and returns a signed token.
POST /api/v1/license/activate
The client calls the public licensing API over HTTPS, caches the signed token, and verifies offline on each start. It only goes online when the token expires or it needs to see a revocation.
Optional: in self-hosted mode, both the service and the database run in your environment.
The user enters a key, the client reports a device id, and the server claims a slot atomically and returns a signed token.
POST /api/v1/license/activate
Verify offline on every start. Call verify when the token nears expiry or you need to observe a revocation.
POST /api/v1/license/verify
Point the updater at the release feed. Add the license key when the feed is gated; artifacts are signed independently.
GET /releases/{product_slug}/*
A device id distinguishes machines, not people. Read the native machine identifier, hash it before upload, and disclose it in your privacy policy.
import { createHash } from 'node:crypto'; import { execSync } from 'node:child_process'; const raw = process.platform === 'win32' ? execSync('reg query "HKLM\\SOFTWARE\\Microsoft\\Cryptography" /v MachineGuid').toString() : execSync('cat /etc/machine-id').toString(); const deviceId = 'wm-' + createHash('sha256') .update('app:' + raw.trim()).digest('hex').slice(0, 32);
| Code | Meaning | Suggested action |
|---|---|---|
ACTIVATION_LIMIT | No device slot left | Send the customer to the portal to release a device |
LICENSE_EXPIRED | License expired | Show renewal; keep read-only features available |
LICENSE_SUSPENDED | License suspended | Ask the customer to contact the owner |
LICENSE_REVOKED | License revoked | Lock paid features |
DEVICE_HELD | Device held | Offer release or retry later |
SEAT_PAUSED | Seat paused | Suggest buying a seat or removing a member |
RATE_LIMITED | Rate limited | Retry with exponential backoff |