1. What activation does
Activation binds one random app installation to a purchased license seat and returns a signed entitlement.
2. Generate an installation ID
Generate UUIDv4 or at least 16 random bytes once per app installation. Keep it in OS-protected local storage.
3. No hardware fingerprint
Do not derive the ID from a MAC address, disk, browser or device serial. Billixi stores only a context-bound HMAC.
4. Call Activate
Send the license key, public product ID, installation ID, SemVer app version and a fresh nonce. Never embed an application secret.
const installationId = crypto.randomUUID() // persist once in OS-protected storage
const clientNonce = crypto.randomUUID() + crypto.randomUUID()
const response = await fetch('https://api.billixi.com/api/v1/payment/public/licenses/v2/activate', {
method: 'POST', headers: { 'content-type': 'application/json' },
body: JSON.stringify({ key, product_id: productId, installation_id: installationId,
app_version: '1.2.3', client_nonce: clientNonce, device_label: 'Work laptop' })
})
if (response.status === 429) scheduleRetry(response.headers.get('retry-after'))
const activation = await response.json()5. Store the refresh token safely
The opaque refresh token is shown only in the response. Store it in Keychain, Keystore, DPAPI or an equivalent protected facility.
6. Rotate with Refresh
Send the current refresh token once. Replace it atomically with the successor returned by the server; retry a lost response with the same nonce.
// Serialize this operation: the current token is single-use.
const next = await post('https://api.billixi.com/api/v1/payment/public/licenses/v2/refresh', {
refresh_token: secureStore.get('refresh_token'), installation_id: installationId,
product_id: productId, client_nonce: freshNonce, app_version: '1.2.3'
})
secureStore.replaceAtomically('refresh_token', next.refresh_token)7. Understand offline grace
The JWT can be verified offline until exp. Revocation cannot invalidate an already issued offline token before that time.
8. Deactivate a seat
Use the current refresh token and installation ID to free the seat, or let the buyer use Library self-service.
{ refresh_token, installation_id, product_id, client_nonce }
9. Handle seat limits
A seat-limit response means another installation must be deactivated first. Do not invent or reuse hardware fingerprints.
10. Apply update policy
The purchase-time policy snapshot controls versions. Same-major uses strict SemVer 2.0 and downloads still require an Approved exact release.
11. Verify JWT with JWKS
Fetch JWKS, select kid, require ES256 and the custom typ, then validate signature, issuer, audience, nbf and exp.
12. Handle key rotation
Cache JWKS according to response headers and refetch once for an unknown kid. Retiring public keys remain visible until their final tokens expire.
13. Error codes
Treat invalid_key/not_found uniformly in user copy; seat_limit_reached is actionable; reuse and compromised states require reactivation; 429 honors Retry-After.
| Code | Recommended action |
|---|---|
| seat_limit_reached | Deactivate another seat. |
| version_not_entitled | Use a version covered by the purchase policy. |
| artifact_withdrawn | Stop distribution and contact the seller. |
| refresh_token_reuse | Discard the compromised session and activate again. |
| policy_snapshot_unresolved | Contact support for Ops backfill review. |
| rate_limit_unavailable | Retry later; the server is failing closed. |
14. Client-side DRM limits
Client-side checks can be removed or patched. Activation raises abuse cost but cannot make a client-controlled application unbreakable.
15. Test environment
Use the public non-production vector to test independent verification. Production credentials, keys and client secrets are never part of the sample.