Sent by the client agent to initiate a payment flow. The requestId serves as an idempotency key.
type AccessRequest = { readonly requestId: string; // UUID, client-generated, idempotency key readonly resourceId?: string; // Seller-defined resource identifier for subscription flows readonly planId?: string; // Must match a Plan.planId for subscription access readonly routeId?: string; // Must match a Route.routeId for route-based access readonly clientAgentId?: string; // DID or URL of the client agent readonly callbackUrl?: string; // Optional async webhook for status updates readonly resource?: { // Standalone route flow: the backend route to call after payment readonly method: string; readonly path: string; readonly body?: unknown; };};
Provide either planId or routeId, not both. The resource field is only used for standalone route-based calls when proxyTo or fetchResource is set on SellerConfig. After payment Key0 proxies to the specified method + path and returns a ResourceResponse instead of an AccessGrant.
Used in: A2A Executor message/send (as a data part), x402 HTTP request parsing.Subscription example:
Sent by the client after completing an on-chain USDC transfer. Contains the transaction hash for verification.
type PaymentProof = { readonly type: "PaymentProof"; readonly challengeId: string; // From the X402Challenge readonly requestId: string; // From the original AccessRequest readonly chainId: number; // Must match the challenge's chainId readonly txHash: `0x${string}`; // On-chain transaction hash readonly amount: string; // Must match the challenge's amount readonly asset: "USDC"; readonly fromAgentId: string; // Client agent identifier};
Used in: A2A Executor message/send (as a data part), ChallengeEngine.submitProof() input.Example:
If the backend returns a non-2xx status, the challenge stays in PAID state (pending refund). The resource.status and resource.body reflect the backend’s error response. For free routes, txHash and explorerUrl are omitted.
The internal record stored in IChallengeStore. Contains all challenge state, timestamps, and the access grant once delivered. Not directly exposed in API responses, but useful for understanding the storage layer.
type ChallengeRecord = { readonly challengeId: string; readonly requestId: string; readonly clientAgentId: string; readonly resourceId: string; readonly planId: string; readonly amount: string; // Dollar string, e.g. "$0.10" readonly amountRaw: bigint; // USDC micro-units, e.g. 100000n readonly asset: "USDC"; readonly chainId: number; readonly destination: `0x${string}`; readonly state: ChallengeState; readonly expiresAt: Date; readonly createdAt: Date; readonly updatedAt: Date; // Auto-updated on every write readonly paidAt?: Date; // Set on PENDING -> PAID readonly txHash?: `0x${string}`; // Set on PENDING -> PAID readonly accessGrant?: AccessGrant; // Set when token is issued (PAID state) readonly fromAddress?: `0x${string}`; // Payer's wallet, set on PENDING -> PAID readonly deliveredAt?: Date; // Set on PAID -> DELIVERED readonly refundTxHash?: `0x${string}`;// Set on REFUND_PENDING -> REFUNDED readonly refundedAt?: Date; // Set on REFUND_PENDING -> REFUNDED readonly refundError?: string; // Set on REFUND_PENDING -> REFUND_FAILED};
Key invariants:
state transitions are atomic via IChallengeStore.transition(id, fromState, toState, updates?, meta?).
amountRaw is the USDC amount in 6-decimal micro-units (e.g., 100000n = $0.10).
accessGrant is persisted durably in the PAID state before the DELIVERED transition, using an outbox pattern.
type Plan = { readonly planId: string; // Unique identifier for this plan readonly unitAmount?: string; // Dollar string, e.g. "$19.00" readonly description?: string; // Human-readable description readonly free?: boolean; // Set true for a free plan};