Offline Licensing
Offline Activation
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
The token and the public key come from two different places in the admin panel — do this in order, since the token is worthless to a customer without your real public key to verify it against.
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 |