1. Get the public keys
public_key is the raw 32-byte key, base64url-encoded. Pick the key whose kid matches the license’s kid. The list can be cached; it changes only when a key is rotated.
2. Rebuild the signed fields
The signature covers exactly these fields, taken from the license or from the verify answer, plus"v": 1:
v, key, env, kid, char_id, provider_team_id, licensee_team_id, scope, scope_version, starts_at, ends_at
They are serialised as JSON with keys sorted at every level and no whitespace. status is not signed.
3. Check the signature
Node
Python
Uses thecryptography package.
2026-12-01T00:00:00.000Z); reformatting a date changes the bytes and the check fails.
The Python and Node versions produce the same bytes for strings, whole numbers, booleans and lists of them, which is everything the known scope fields hold. If you add your own scope fields, keep them to those types: a decimal such as 1.0 is written differently by the two languages.