License token contract

This is the contract the Bloodraven operator verifies offline. Tokens are minted on demand at /license from a Polar order. The operator never calls Polar; it only checks this JWT.

This is the contract the Bloodraven operator verifies offline. Tokens are minted on demand at /license from a Polar order. The operator never calls Polar; it only checks this JWT.

The token is a compact JWS. Polar's native license keys are opaque UUID4 strings validated against Polar's API and cannot be verified offline, so Bloodraven issues this JWT instead.

FieldTypeRequiredValue
algstringyesExactly EdDSA. Anything else, including none, Ed25519, HS256, and ES256, is rejected before the signature is checked.
kidstringyesNon-empty key id. Must match an entry in the operator's append-only trust store.
typstringnoIf present, must be JWT.

Claims

ClaimTypeRequiredMeaning
issstringyesExactly https://license.shipstream.io/bloodraven.
substringyesCustomer / subject id. Non-empty.
orgstringyesOrganization display name. Used as the Prometheus organization label.
editionstringyesproduction or organization.
issuedForstringyesPolar order id, for support correlation.
iatnumberyesUnix seconds. Integer, ≥ 0. The operator allows 7 days of clock skew into the future.
nbfnumbernoUnix seconds. Same 7-day future leeway if present.
updatesUntilnumberyesUnix seconds. End of the paid update period. Not license expiry. A past value is the supported perpetual state.
exp—omitDo not include this claim. The product is a perpetual license plus 12 months of updates. If exp is the update-period end, every conformant JWT library will reject the token after 12 months and the operator will be unable to read org or edition. The operator ignores exp if a buggy signer includes it.

Do not put the update-period end in exp. Put it in updatesUntil.

Size limits the operator enforces (larger values are malformed):

LimitValue
Compact token8192 bytes
Decoded header1024 bytes
Decoded payload4096 bytes

Signature

Ed25519 over base64url(header) + "." + base64url(payload) (unpadded base64url). The signature segment is the raw 64-byte signature, also unpadded base64url.

The operator never fetches keys, JWKS, or a revocation list. The public key for each kid is compiled into the binary. Keys are append-only: retired kids stay in the store so old tokens keep verifying.

Renewal

Fetch the token again at /license with the Polar order ID. If Polar attached a subscription to that order, updatesUntil comes from the current period end. If the renewal is a separate one-time order, use that order ID. A renewal order only mints a token when the same Polar customer (the same checkout email) has an earlier paid, unrefunded Production or Organization purchase of the same edition. Replace spec.license or the operator license value with the new string. The operator just verifies it.

A first purchase with no subscription mints updatesUntil from the order's created_at plus 12 months (or the commercial term in force).

Worked example

This token is signed with a test-only key. The production operator will report it as unknown kid. Do not ship this key.

Header:

{"alg":"EdDSA","kid":"test-only-1","typ":"JWT"}

Payload:

{
  "edition": "organization",
  "iat": 1755216000,
  "iss": "https://license.shipstream.io/bloodraven",
  "issuedFor": "ord_example",
  "org": "Acme Corp",
  "sub": "cus_example",
  "updatesUntil": 1786752000
}

iat is 2025-08-15T00:00:00Z. updatesUntil is 2026-08-15T00:00:00Z. There is no exp.

Compact token:

eyJhbGciOiJFZERTQSIsImtpZCI6InRlc3Qtb25seS0xIiwidHlwIjoiSldUIn0.eyJlZGl0aW9uIjoib3JnYW5pemF0aW9uIiwiaWF0IjoxNzU1MjE2MDAwLCJpc3MiOiJodHRwczovL2xpY2Vuc2Uuc2hpcHN0cmVhbS5pby9ibG9vZHJhdmVuIiwiaXNzdWVkRm9yIjoib3JkX2V4YW1wbGUiLCJvcmciOiJBY21lIENvcnAiLCJzdWIiOiJjdXNfZXhhbXBsZSIsInVwZGF0ZXNVbnRpbCI6MTc4Njc1MjAwMH0.MlHGkwxyk325K5RWI_rIYCLkFBzmRWD6jTa2OQ2zp9rHgjW6Fy1gT_V93T2WHzQebLnJhkH8eKDe-YwQyLFkDg

Test-only public key (32 bytes, hex), not in the production trust store:

4cb5abf6ad79fbf5abbccafcc269d85cd2651ed4b885b5869f241aedf0a5ba29

Inserting the production public key

Generate the keypair on the signer host. Never commit the private key.

openssl genpkey -algorithm ED25519 -out license-ed25519.pem
openssl pkey -in license-ed25519.pem -pubout -outform DER | tail -c 32 | xxd -p -c 32

Add the 64 hex characters to internal/license/keys.go as "br-YYYY-N": "<hex>". A build with no entries compiles and treats every token as unknown kid (Community behavior, valid="false"). It does not panic.