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.
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.
Header
| Field | Type | Required | Value |
|---|---|---|---|
alg | string | yes | Exactly EdDSA. Anything else, including none, Ed25519, HS256, and ES256, is rejected before the signature is checked. |
kid | string | yes | Non-empty key id. Must match an entry in the operator's append-only trust store. |
typ | string | no | If present, must be JWT. |
Claims
| Claim | Type | Required | Meaning |
|---|---|---|---|
iss | string | yes | Exactly https://license.shipstream.io/bloodraven. |
sub | string | yes | Customer / subject id. Non-empty. |
org | string | yes | Organization display name. Used as the Prometheus organization label. |
edition | string | yes | production or organization. |
issuedFor | string | yes | Polar order id, for support correlation. |
iat | number | yes | Unix seconds. Integer, ≥ 0. The operator allows 7 days of clock skew into the future. |
nbf | number | no | Unix seconds. Same 7-day future leeway if present. |
updatesUntil | number | yes | Unix seconds. End of the paid update period. Not license expiry. A past value is the supported perpetual state. |
exp | — | omit | Do 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):
| Limit | Value |
|---|---|
| Compact token | 8192 bytes |
| Decoded header | 1024 bytes |
| Decoded payload | 4096 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.

