Subscription plans only. Token issuance applies only to plan purchases. For route-based pay-per-call flows,
fetchResourceCredentials is not called:- Embedded per-request — Key0 settles on-chain and calls
next(), passing control to your route handler. No JWT is issued;req.key0Paymentcontains payment metadata. - Standalone per-request — Key0 settles on-chain and proxies the request to your backend via
fetchResource/proxyTo. The backend’s response is returned directly as aResourceResponse.
Token issuance flow
Challenge transitions to PAID
The
ChallengeEngine verifies the on-chain payment and transitions the challenge from PENDING to PAID.Callback returns credentials
Your callback returns
{ token: string } (or any credential shape you define).AccessTokenIssuer
TheAccessTokenIssuer class handles JWT creation and verification. It supports two signing algorithms: HS256 (shared secret) and RS256 (RSA key pair).
Constructor
The constructor accepts either a plain string (backward-compatible) or a config object:HS256 secrets must be at least 32 characters. The constructor throws immediately if the secret is shorter.
Config type
Methods
sign
Creates a signed JWT with the given claims and TTL.verify
Verifies a token using the primary secret. HS256 only — RS256 tokens should be verified withvalidateKey0Token using the public key.
verifyWithFallback
Tries the primary secret first, then falls back to a list of previous secrets. Use this during secret rotation.TokenClaims
Every Key0 JWT contains these custom claims alongside the standardiat and exp:
| Claim | Type | Description |
|---|---|---|
sub | string | The requestId that initiated the payment flow |
jti | string | The challengeId for this specific challenge |
resourceId | string | The resource the agent paid to access |
planId | string | The pricing plan the agent selected |
txHash | string | The on-chain transaction hash proving payment |
iat | number | Issued-at timestamp (seconds since epoch) |
exp | number | Expiration timestamp (seconds since epoch) |
validateToken middleware
ThevalidateToken function is a framework-agnostic utility that extracts and verifies a Bearer token from an Authorization header. The framework integrations (Express, Hono, Fastify) wrap this function into middleware.
Error handling
validateToken throws a Key0Error with the following codes:
| Condition | Error code | HTTP status |
|---|---|---|
Missing or malformed Authorization header | INVALID_REQUEST | 401 |
| Expired token | CHALLENGE_EXPIRED | 401 |
| Invalid signature or tampered token | INVALID_REQUEST | 401 |
validateKey0Token (lightweight validator)
If your backend service only needs to verify tokens and does not need the full SDK, use the standalone validator from@key0ai/key0/validator. It supports both HS256 and RS256 and has no blockchain dependencies.
sub, jti, resourceId, planId, txHash) and throws if any are missing.
HS256 vs RS256
| HS256 | RS256 | |
|---|---|---|
| Key type | Shared secret (>= 32 chars) | RSA private/public key pair |
| Sign with | Secret | Private key (PEM) |
| Verify with | Same secret | Public key (PEM) |
| Use case | Single service, or services that share the same secret | Distributed verification — sign on one server, verify on many without sharing the private key |
| Rotation | verifyWithFallback with old secrets | Publish new public key, keep old key in rotation |
Token issuance timeout
TheChallengeEngine wraps your fetchResourceCredentials callback in a Promise.race with a configurable timeout.
tokenIssueRetries times with exponential backoff (500ms base delay — 500ms, 1s, 2s, and so on).
If token issuance fails permanently, the challenge stays in the PAID state. The refund cron can pick it up and settle an on-chain refund automatically.
