Skip to content

Runtime Verification

Use this page to verify signed runtime responses correctly before you ship an application-side integration.

Who This Is For

  • developers integrating runtime validation into an app
  • security-conscious teams that need tamper-evident runtime responses
  • AI agents implementing activation, validation, metering, offline, or floating flows

When To Use This

Read this page whenever you call a runtime endpoint under /api/v1/license/....

Runtime verification is not optional. The runtime success envelope is only trustworthy after you verify the signature over the data payload.

How It Works

Signed runtime surface

Runtime success responses use:

json
{
  "data": { "...": "..." },
  "signature": { "...": "..." },
  "meta": { "...": "..." }
}

The signature covers the JSON bytes of data.

This applies to runtime responses such as:

  • activation
  • validation
  • check
  • consume
  • deactivate
  • floating checkout
  • floating heartbeat
  • floating checkin

Offline issuance is slightly different:

  • POST /api/v1/license/offline returns an encrypted envelope
  • after decryption, the inner payload is signed and must still be verified

Public keys

Fetch verification keys from:

text
GET /api/v1/system/public-keys

Key rotation matters:

  • new responses use the active signing key
  • retired public keys stay published so older signed payloads can still be verified
  • clients should match by kid and not assume one permanent key

Verification flow

  1. call the runtime endpoint with Authorization: License <license-key>
  2. read data, signature, and signature.kid
  3. fetch or load public keys from the system endpoint
  4. verify the signature against the exact data payload bytes
  5. only then trust status, features, expires_at, or version_eligibility

Example

TypeScript verification with the first-party SDK:

ts
import {
  PublicKeyStore,
  RuntimeClient,
  SystemClient,
  verifyRuntimeResult
} from "@licensekit/sdk";

const baseUrl = "https://api.licensekit.dev";

const runtime = new RuntimeClient({
  baseUrl,
  licenseKey: process.env.LICENSE_KEY!
});

const system = new SystemClient({ baseUrl });

const result = await runtime.validateLicense({
  body: {
    fingerprint: "workstation-01",
    app_version: "1.2.0"
  }
});

const publicKeys = await system.listPublicKeys();
const verification = await verifyRuntimeResult(
  result,
  new PublicKeyStore(publicKeys.data)
);

if (!verification.ok) {
  throw new Error(`verification failed for key ${result.signature.kid}`);
}

if (result.data.status !== "active") {
  throw new Error(`license is not active: ${result.data.status}`);
}

For offline usage, decrypt the offline envelope first, then apply the same verification rule to the inner signed payload.

Common Mistakes

  • trusting data before checking the signature
  • verifying the whole envelope instead of the data payload
  • dropping retired public keys and breaking verification after rotation
  • treating offline envelopes as already verified after decryption
  • assuming /health or /readyz has anything to do with runtime signature trust

Prototype docs shell for the rewrite workspace.