A2A JSON-RPC
basePath is /a2a, making the full path /a2a/jsonrpc.
The x402 HTTP middleware sits in front of this endpoint and intercepts message/send calls. If the request does not include an X-A2A-Extensions header, the middleware handles the payment flow directly via HTTP 402 responses. If the header is present, the request passes through to the standard A2A JSON-RPC handler.
x402 HTTP Flow (no X-A2A-Extensions header)
When a client sends amessage/send JSON-RPC call without the X-A2A-Extensions header, the x402 middleware intercepts it and implements a two-step payment flow.
Step 1: Request Access
Send anAccessRequest in the message parts to receive an x402 payment challenge.
payment-required header (base64-encoded).
| Header | Value |
|---|---|
payment-required | Base64-encoded JSON of the payment requirements |
www-authenticate | Payment realm="https://api.example.com", accept="exact", challenge="http-..." |
Step 2: Submit Payment
Sign an EIP-3009transferWithAuthorization off-chain, encode the payment payload as base64url, and resend the same request with the PAYMENT-SIGNATURE header.
| Header | Value |
|---|---|
payment-response | Base64-encoded X402SettleResponse JSON |
A2A-Native Flow (with X-A2A-Extensions header)
When a client sends theX-A2A-Extensions header, the x402 middleware passes through to the standard A2A JSON-RPC handler. In this mode, two skills are available:
Skill: request-access
Issues anX402Challenge via the A2A protocol.
Request:
Skill: submit-proof
Submits aPaymentProof with the on-chain transaction hash. Returns an AccessGrant on success.
Request:
Error Handling
All errors in the x402 HTTP flow return standard JSON error responses:| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | TIER_NOT_FOUND | The requested planId does not exist in the plan catalog. |
| 400 | INVALID_REQUEST | Malformed request body or missing required fields. |
| 402 | PAYMENT_FAILED | Payment verification or settlement failed. |
| 409 | TX_ALREADY_REDEEMED | The transaction hash has already been used for another challenge. |
| 410 | CHALLENGE_EXPIRED | The challenge has expired or was cancelled. |
| 500 | INTERNAL_ERROR | Unexpected server error or concurrent state transition conflict. |
Idempotency
TherequestId field serves as an idempotency key. If a client retries the same requestId:
- PENDING challenge exists: Returns the same challenge without creating a new one.
- DELIVERED grant exists: Returns the existing
AccessGrantdirectly. - EXPIRED or CANCELLED: Creates a new challenge.
Related
A2A Payment Flow
End-to-end walkthrough of the Agent-to-Agent payment protocol.
Data Models
TypeScript types for AccessRequest, X402Challenge, PaymentProof, and AccessGrant.
ChallengeEngine
The state machine that powers challenge lifecycle and payment verification.
Error Codes
Full reference of Key0ErrorCode values and HTTP status codes.

