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.

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 Production or Enterprise. See Plans & Entitlements.
  • A client-scoped Keel API key for POST /v1/execute.

Set your Keel environment:

export KEEL_BASE_URL="https://api.keelapi.com" export KEEL_API_KEY="keel_sk_your_project_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.

Quick start

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" }, "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).

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.
cadence"one_shot" for one-shot purchases; "recurring" for subscriptions.
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": "perm_x402_001", "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, "response_headers": { "payment-response": "..." } }, "provider_attestation": { "provider_protocol": "x402", "normalizer_version": "x402.http.v2", "trust_domain": "provider_principal" }, "verifier": { "status": "PASS" } }
  • permit.decision reflects the policy decision ("allow", "deny", "challenge").
  • execution.rail_outcome reflects the rail result ("paid" or "failed").
  • execution.settlement_status reflects the settlement state ("settled", "failed", or "awaiting_counter_signature").
  • 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 paymentprimary_outcome: "denied". No settlement is attempted. The denial decision is bound into the permit.
Policy requires step-upprimary_outcome: "challenged". The execution holds; settle the challenge before retrying.
Settlement failsrail_outcome: "failed" with provider_attestation.error_reason. The permit still records the attempted payment for audit.
Settlement times outrail_outcome: "pending". Use the reconciliation API to query for the final state.

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