Offline Licensing
Offline Activation
For Store subscriptions, newly generated offline tokens expire no later than paid-through plus the effective payment grace. Reconnect to obtain an updated signed token after renewal. Already-issued offline tokens retain their signed deadline while disconnected.
Offline activation lets customers activate a license key on a machine that has no internet access. Instead of calling the public API, the customer presents a signed offline token that was generated for their specific device by a Company Admin.
How it works
- Customer generates their hardware fingerprint (HWID) and sends it to you via email / support portal.
- Admin opens the license in PermitCore → scrolls to Offline Activation Tokens → clicks Generate Token.
- Admin enters the Device ID, optional device name, and how long the token should be valid (1–365 days).
- A signed token is shown once — admin copies and sends it to the customer.
- Customer's application calls
ActivateOffline(token)in your SDK. The SDK verifies the ECDSA signature, expiry, and device binding, then stores the token locally. - On every subsequent launch, the SDK verifies the cached token offline — no internet needed.
GET /api/v1/{tenantSlug}/public-key — the SDK uses it for verification; provide or cache it before disconnecting.
Distribute the trusted public key to an air-gapped machine before activation. Offline verification works only until the token expires; the machine cannot discover revocations while disconnected.
Step-by-step in the Admin Panel
Obtain the public verification key from the public API and generate the device token in Admin. Prepare both before disconnecting the customer machine.
GET /api/v1/{tenantSlug}/public-key and keep the returned publicKey and kid with your application or offline package. The public verification key can verify signatures, not create them. Enterprise Export Signing Key returns private signing material as well; keep that vendor recovery package private and distribute only the public verification key to customers.GetHardwareId()) and sends you the resulting string — by email, support ticket, or an in-app "Copy Device ID" button you build. This is the value the token gets bound to.pc_offline_v1.… string is displayed exactly once. Copy it now and send it to the customer alongside the public key from step 1 (not the token alone — the customer's app needs both to verify).Token format
Tokens have the format pc_offline_v1.<payload>.<signature>:
| Part | What it contains |
|---|---|
pc_offline_v1 | Version prefix — validated first by the SDK |
<payload> | Base64URL-encoded JSON: tokenId, licenseKeyHash, deviceId, productName, expiresAt, … |
<signature> | Base64URL-encoded ECDSA P-256/SHA-256 signature over the UTF-8 bytes of the base64url payload string |
What the SDK does
- Split on
., check prefix ispc_offline_v1 - Fetch (or load cached) tenant public key from
/api/v1/{slug}/public-key - Verify ECDSA signature — reject if invalid or tampered
- Check
expiresAt> now — reject if expired - Check
deviceIdmatches local hardware fingerprint — reject if different machine - Store token on disk — verified offline on every future launch
var client = new PermitCoreClient("https://your-domain.com"); var publicKey = /* fetched once from GET /api/v1/{slug}/public-key, then cached */; // Offline activate (customer sent you the token) — persists to local disk var result = PermitCoreClient.ActivateOffline(offlineToken, publicKey, hwid); // On every subsequent launch — no internet needed var result = PermitCoreClient.ValidateOffline(hwid);
# pip install cryptography (lazy-imported — base client stays dependency-free) result = PermitCoreClient.activate_offline(offline_token, public_key, hwid) # On every subsequent launch — no internet needed result = PermitCoreClient.validate_offline(hwid)
const result = client.activateOffline(offlineToken, publicKey, hwid); // On every subsequent launch — no internet needed const result = client.validateOffline(hwid);
$result = $client->activateOffline($offlineToken, $publicKey, $hwid);
// On every subsequent launch — no internet needed
$result = $client->validateOffline($hwid);OfflineTokenResult result = client.activateOffline(offlineToken, publicKey, hwid);
// On every subsequent launch — no internet needed
OfflineTokenResult result = client.validateOffline(hwid);auto result = client.activate_offline(offline_token, public_key, hwid); // On every subsequent launch — no internet needed auto result = permitcore::PermitClient::validate_offline(hwid);
Revoking a token
To revoke an offline token, go to the license detail page → Offline Activation Tokens → click the ban icon next to the token. The token is immediately marked as revoked in the database.
Revoking is soft enforcement: the SDK can optionally call
POST /api/v1/offline/verify to check revocation status when online.
Strictly offline deployments won't see revocations until the token naturally expires.
Use short expiresInDays values (e.g. 30–90 days) for tighter control.
Optional server-side verification
When the customer's machine has internet access, your SDK can also call the server to check for revocation:
{
"token": "pc_offline_v1.eyJ2ZXJzaW9uIjoxLCJ..."
}{
"isValid": true,
"tokenId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"productName": "Acme Video Editor Pro",
"deviceId": "a3f8d2b1c9e4...",
"expiresAt": "2026-08-29T00:00:00Z",
"message": "Token valid."
}Security properties
| Property | How it's achieved |
|---|---|
| Tamper-proof | ECDSA P-256 signature — any modification invalidates the signature |
| Device-bound | Token payload includes deviceId — SDK rejects tokens for other machines |
| Time-limited | expiresAt checked by both SDK and server verify endpoint |
| Revocable | TokenId stored server-side — checked via /offline/verify when online |
| Public key distribution | Public key is per-tenant, fetched once from /api/v1/{slug}/public-key |