Skip to Content

x402

x402  is an open, HTTP-native payments protocol from the x402 Foundation. The protocol piggybacks on the HTTP 402 Payment Required status code: a resource server challenges a client with one or more payment options, the client submits a signed payment payload, and the server returns the resource along with a settlement receipt. The most common deployment today is USDC payments on Base (mainnet or base-sepolia for development), but the protocol is network-agnostic.

Keel supports x402 across both rungs:

  • Evidence: x402 challenge, payload, and settlement receipt can be bound into a signed permit at resource_attributes_json.x402 for tamper-evident audit.
  • Runtime: Keel’s managed-path dispatcher drives the full HTTP 402 → pay → retry → receipt → bind sequence under one permit. Your app calls POST /v1/execute; Keel handles the rail.

Use this guide when your application wants to:

  • Pay for an x402-protected resource through Keel’s managed-path runtime, OR
  • Bind a self-executed x402 payment into Keel for audit and policy adjudication.

Beta. This rail is tested against the documented request shape but has not been certified end to end against a live merchant or facilitator.

Keel does not custody payer wallet keys for production runtime today. The managed-path runtime expects your application to pass a short-lived x402 payment payload or signature per call. Keel decides whether the agent may spend, relays the payment proof to the paid resource, and binds the resulting receipt evidence.

Architecture

Customer app | | 1. POST /v1/execute with action_verb=payment.execute, | target URL, x402 spend authority, requested amount, | and a customer-provided x402 payment payload v Keel | | 2. Decide against policy and spend authority | 3. POST <target URL> on the resource server v Resource server | | 4. Return 402 Payment Required with PaymentRequirements | (scheme, network, amount, asset, payTo, ...) v Keel | | 5. Validate the supplied x402 PaymentPayload shape | 6. Retry: POST <target URL> with PAYMENT-SIGNATURE v Resource server | | 7. Verify with facilitator, settle on-chain, | return resource + PAYMENT-RESPONSE v Keel | | 8. Bind PaymentRequired, PaymentPayload, and | SettlementResponse into resource_attributes_json.x402 | 9. Substrate-v6 binding hash recursively covers | the x402 evidence — tamper-evident on verify v Customer app

The trust domains stay separate:

DomainOwnerKeel role
Wallet credentialsYour app or wallet serviceKeel receives only the per-call x402 payment payload/signature. It does not hold wallet keys.
Spend decisionKeel permit policyDecide whether this agent may spend this much on this resource.
Settlement railResource server + facilitator (Coinbase, Cloudflare, or self-hosted) + the underlying network (Base, Solana, etc.)Record rail outcome and bind the receipt evidence.
Audit trailKeel + the on-chain transactionPreserve tamper-evident audit evidence for review.

Prerequisites

  • An x402-protected resource server endpoint, or a sandbox facilitator (the x402 Foundation runs a public facilitator suitable for development).
  • A wallet on the target network with funds in the required asset (USDC on base-sepolia is the standard development setup).
  • A Keel project on any plan. Starter cannot author an explicit allow rule, so a Starter project allows payments by applying the AI Permit-to-Pay policy template; Production and Enterprise can also write their own allow rules. See Plans & Entitlements.
  • A server-only unbound Client key held by the trusted customer gateway; see the setup below.

Set your Keel environment:

export KEEL_BASE_URL="https://api.keelapi.com" export KEEL_API_KEY="<server-only unbound gateway Client key>"

For the managed-path runtime, your app creates or obtains the x402 payment payload before calling Keel. Do not put wallet private keys in an agent process.

Configure the trusted payment gateway

Both rails share one gateway setup with a server-only Client key not bound to an AgentPrincipal. Complete Configure the trusted payment gateway first.

Quick start

Run execution from the trusted gateway configured above. The request below is a shape example: use a current signed payload for the real resource, network, asset, recipient, nonce, and validity window. Placeholder signatures and historical dates do not authorize settlement. A funded wallet and an eligible resource server/facilitator remain external prerequisites.

Your application signs or obtains an x402 PaymentPayload, then sends that payload to Keel with the target URL and spend authority. Keel decides whether the agent may spend before it dispatches the paid request.

Keel performs this runtime flow:

  1. Create a permit for action_verb: "payment.execute".
  2. Call the target URL.
  3. Require an HTTP 402 response with PAYMENT-REQUIRED.
  4. Retry with PAYMENT-SIGNATURE.
  5. Require PAYMENT-RESPONSE on success.
  6. Persist the provider attestation and permit evidence.

Request shape

The x402 rail uses the same POST /v1/execute envelope as Stripe MPP. Set provider to x402 and model to x402.http.v2.

{ "provider": "x402", "model": "x402.http.v2", "action_verb": "payment.execute", "input": { "requested_amount": 500, "requested_currency": "usdc", "mpp_target_url": "https://merchant.example/x402/report.pdf", "authority": { "amount_max": 500, "currency_class": "USDC_STABLE", "cadence": "one_shot", "ttl_seconds": 3600, "purpose_binding": "purchase.once" }, "spend_request_payload": { "id": "lsrq_x402_123", "amount": 500, "currency": "usdc", "status": "approved", "credential_type": "x402_payment_payload", "x402": { "payment_payload": { "x402Version": 2, "resource": { "url": "https://merchant.example/x402/report.pdf", "description": "Quarterly report", "mimeType": "application/pdf" }, "accepted": { "scheme": "exact", "network": "eip155:84532", "amount": "500", "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", "payTo": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } }, "payload": { "signature": "0x...", "authorization": { "from": "0x857b06519E91e3A54538791bDbb0E22373e36b66", "to": "0x209693Bc6afc0C5328bA36FaF03C514EF312287C", "value": "500", "validAfter": "1740672089", "validBefore": "1740672154", "nonce": "0x..." } }, "extensions": {} } } } } }

spend_request_payload.x402.payment_signature is also accepted when your application already has the base64-encoded x402 payment payload.

The field name mpp_target_url is retained in the current API shape for both Stripe MPP and x402. It means “the paid resource endpoint Keel should call.”

Evidence binding

When a customer executes x402 outside Keel and wants to bind the evidence into a permit, the x402 evidence lives at resource.attributes.x402 on POST /v1/permits.

{ "subject": { "type": "agent", "id": "agent_x402", "attributes": {} }, "action": { "name": "payment.execute", "attributes": {} }, "resource": { "type": "payment", "id": "x402_report_purchase", "attributes": { "provider": "x402", "model": "x402.http.v2", "operation": "cost_permit.authorize", "modality": "payment", "execution_mode": "sync", "x402": { "protocol": "x402", "protocol_version": 2, "transport": "http", "payment_required": { "x402Version": 2, "error": "payment required", "resource": { "url": "https://merchant.example/x402/report.pdf", "description": "Quarterly report", "mimeType": "application/pdf" }, "accepts": [ { "scheme": "exact", "network": "base-sepolia", "amount": "10000", "asset": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "payTo": "0x1111111111111111111111111111111111111111", "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "decimals": 6 } } ] }, "payment_payload": { "x402Version": 2, "resource": { "url": "https://merchant.example/x402/report.pdf" }, "accepted": { "scheme": "exact", "network": "base-sepolia", "amount": "10000", "asset": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee", "payTo": "0x1111111111111111111111111111111111111111", "maxTimeoutSeconds": 60 }, "payload": { "from": "0x2222222222222222222222222222222222222222", "authorization": "0xauthorization", "signature": "0xsignature" } }, "settlement_response": { "success": true, "errorReason": null, "transaction": "0xabc...", "network": "base-sepolia", "payer": "0x2222222222222222222222222222222222222222", "amount": "10000" }, "payment_required_b64": "eyJ4NDAyVmVyc2lvbiI6Mn0=", "payment_signature_b64": "c2lnbmF0dXJl", "payment_response_b64": "eyJzdWNjZXNzIjp0cnVlfQ==", "request_method": "POST", "request_url": "https://merchant.example/x402/report.pdf", "http_status": 200, "response_body_sha256": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "observed_at": "2026-07-07T12:00:00Z" } } } }

X402Evidence reference

The structure under resource_attributes_json.x402 mirrors the x402 wire protocol so any third-party verifier can re-derive the payment from the captured payload.

FieldTypeDescription
protocolstringAlways "x402".
protocol_versionintegerx402 protocol version (currently 2).
transportstringWire transport ("http" for standard HTTP 402; reserved for future transports).
payment_requiredobjectThe original 402 challenge returned by the resource server, including the accepts array of PaymentRequirements.
payment_payloadobjectThe signed PaymentPayload submitted on the retry request.
settlement_responseobjectThe receipt returned by the resource server after settlement (parsed from PAYMENT-RESPONSE).
payment_required_b64stringRaw base64 PAYMENT-REQUIRED header from the 402 challenge.
payment_signature_b64stringRaw base64 PAYMENT-SIGNATURE header sent on the retry.
payment_response_b64stringRaw base64 PAYMENT-RESPONSE header returned after settlement.
request_methodstringHTTP method of the paid request.
request_urlstringURL of the paid request.
http_statusintegerHTTP status of the paid response.
response_body_sha256stringLowercase hex SHA-256 of the paid response body.
observed_atstringTime the exchange was observed.

All fields except protocol, protocol_version, and transport are required.

The full schemas for each nested object are published in openapi.json under X402Evidence, X402PaymentRequired, X402PaymentRequirements, X402ResourceInfo, X402PaymentPayload, and X402SettlementResponse.

Spend authority

x402 uses the same spend authority shape as Stripe MPP, with currency_class extended to cover stablecoins and on-chain assets:

FieldNotes
amount_maxAtomic units in the asset’s base denomination (e.g. for USDC with 6 decimals, 10000 = 0.01 USDC).
currency_class"USDC_STABLE" for USDC, "USD_FIAT" for fiat-denominated rails, others as defined.
cadenceMust be "one_shot". Any other value returns 400 MPP_CADENCE_NOT_SUPPORTED.
ttl_secondsBounded; Keel rejects authorities older than the TTL at execution time.

Response shape

The POST /v1/execute response uses the standard execution envelope. For successful x402 payments:

{ "id": "exec_7f4c...", "object": "execution", "status": "completed", "action_verb": "payment.execute", "permit": { "permit_id": "31d66d92-4e90-4dc6-9278-0b86551e31bc", "decision": "allow", "reason_code": "mpp.unit_priced_policy" }, "execution": { "permit_outcome": "allowed", "rail_outcome": "paid", "settlement_status": "settled", "primary_outcome": "allowed_and_paid", "execution_id": "8b1b..." }, "output": { "rail_outcome": "paid", "settlement_status": "settled", "http_status": 200, "resource": "Example paid resource" }, "provider_attestation": { "provider_protocol": "x402", "normalizer_version": "x402.http.v2", "trust_domain": "provider_principal" }, "verifier": { "status": "PASS" } }
  • permit.decision reflects the public policy decision ("allow", "deny", "review", or "throttle").
  • execution.rail_outcome reflects the rail result ("paid", "failed", "not_attempted", or "outcome_unknown").
  • execution.settlement_status reflects the settlement state ("settled", "failed", "not_applicable", "awaiting_counter_signature", "challenge_expired", or "reconciliation_required").
  • The substrate-v6 binding hash recursively covers all rail evidence. Any post-issuance change to resource_attributes_json.x402 is detectable on verification.

Error handling

ScenarioBehavior
Resource server does not return 402Keel does not infer payment is required. Your app must initiate the x402 flow explicitly.
Policy denies the paymentHTTP 403 with the execution envelope as the body: primary_outcome: "denied", rail_outcome: "not_attempted". No settlement is attempted. The denial decision is bound into the permit. SDK clients raise KeelError for non-2xx responses; read the envelope from the error body.
Policy requires reviewexecution.primary_outcome: "challenge_awaiting_counter_signature", rail_outcome: "not_attempted", and settlement_status: "awaiting_counter_signature". No paid dispatch occurs. Native payment resume requires a valid buyer-principal counter-signature bound to the exact persisted execution intent; an ordinary model-review approval is not a substitute.
Resource server returns a failed payment responseexecution.primary_outcome: "allowed_but_rail_failed" and rail_outcome: "failed"; inspect the execution envelope error and HTTP status. A provider attestation may be absent. Authorization does not prove settlement.
Timeout or connection loss after possible paid dispatchHTTP 409, execution.primary_outcome: "allowed_but_outcome_unknown", rail_outcome: "outcome_unknown", and settlement_status: "reconciliation_required". The effect is unknown, not a safely retryable payment. Do not issue a new idempotency key or re-send the credential. Reconciliation requires an authoritative provider-signed receipt bound to this execution; it is not a generic status query.

Native reviewed payment resumption uses POST /v1/execute/{execution_id}/resume with the same gateway Client credential and fresh request headers after the required buyer counter-signature. The persisted request is re-gated; changing the payment intent or supplying an unrelated signature does not release it.

Audit trail

The signed permit binds the x402 evidence into the v6 resource_attributes_canonical_hash. To verify offline:

  1. Export the permit through Signed Exports.

  2. Install keel-verifier:

    pip install keel-verifier keel-verify export <export-file> --json
  3. The verifier recomputes resource_attributes_canonical_hash from the raw resource_attributes_json and compares it to the signed value. Any tampering with the x402 payment payload, settlement response, or any other rail evidence is detected as permit.binding.v6.resource_attributes_canonical_hash_mismatch.

You can also verify with any RFC 8785 implementation in any language — no Keel-specific code is required. See Independent Verification.

FAQ

Does Keel support networks other than Base?

The x402 protocol is network-agnostic. Keel’s evidence path accepts any network the x402 spec defines (base, base-sepolia, Solana, Stellar, future EVM networks). The runtime path is currently optimized for Base (mainnet + sepolia); other networks are supported on the evidence path and runtime support follows customer signal.

What schemes are supported?

Today: exact (the most common, used for single-purchase pricing). The x402 protocol defines upto and batch-settlement schemes; Keel’s evidence path accepts them, and runtime support tracks customer demand.

Do I have to use a public facilitator?

No. The facilitator handles payment verification and settlement on the resource server’s side. Public facilitators (Coinbase, Cloudflare) are convenient for development; production deployments often self-host. Keel’s evidence path is facilitator-agnostic; the settlement_response shape is the same regardless.

Can I combine x402 with other Keel features?

Yes. x402 permits compose with Budget Envelopes, Parent-Child Verb Delegation, Workflow Intent, and the rest of the Keel substrate identically to Stripe MPP. The payment.execute action verb is shared.

Is x402 evidence tamper-evident even after Keel signs it?

Yes. Substrate-v6 binds the full resource_attributes_json mapping (including resource_attributes_json.x402) into the signed permit binding hash. Any change to any nested field of the x402 evidence is detectable on independent verification.

Last updated on Edit this page on GitHubÂ