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.x402for 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 appThe trust domains stay separate:
| Domain | Owner | Keel role |
|---|---|---|
| Wallet credentials | Your app or wallet service | Keel receives only the per-call x402 payment payload/signature. It does not hold wallet keys. |
| Spend decision | Keel permit policy | Decide whether this agent may spend this much on this resource. |
| Settlement rail | Resource server + facilitator (Coinbase, Cloudflare, or self-hosted) + the underlying network (Base, Solana, etc.) | Record rail outcome and bind the receipt evidence. |
| Audit trail | Keel + the on-chain transaction | Preserve 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-sepoliais the standard development setup). - A Keel project on any plan. Starter cannot author an explicit
allowrule, so a Starter project allows payments by applying the AI Permit-to-Pay policy template; Production and Enterprise can also write their ownallowrules. 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:
- Create a permit for
action_verb: "payment.execute". - Call the target URL.
- Require an HTTP 402 response with
PAYMENT-REQUIRED. - Retry with
PAYMENT-SIGNATURE. - Require
PAYMENT-RESPONSEon success. - 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.
| Field | Type | Description |
|---|---|---|
protocol | string | Always "x402". |
protocol_version | integer | x402 protocol version (currently 2). |
transport | string | Wire transport ("http" for standard HTTP 402; reserved for future transports). |
payment_required | object | The original 402 challenge returned by the resource server, including the accepts array of PaymentRequirements. |
payment_payload | object | The signed PaymentPayload submitted on the retry request. |
settlement_response | object | The receipt returned by the resource server after settlement (parsed from PAYMENT-RESPONSE). |
payment_required_b64 | string | Raw base64 PAYMENT-REQUIRED header from the 402 challenge. |
payment_signature_b64 | string | Raw base64 PAYMENT-SIGNATURE header sent on the retry. |
payment_response_b64 | string | Raw base64 PAYMENT-RESPONSE header returned after settlement. |
request_method | string | HTTP method of the paid request. |
request_url | string | URL of the paid request. |
http_status | integer | HTTP status of the paid response. |
response_body_sha256 | string | Lowercase hex SHA-256 of the paid response body. |
observed_at | string | Time 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:
| Field | Notes |
|---|---|
amount_max | Atomic 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 | Must be "one_shot". Any other value returns 400 MPP_CADENCE_NOT_SUPPORTED. |
ttl_seconds | Bounded; 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.decisionreflects the public policy decision ("allow","deny","review", or"throttle").execution.rail_outcomereflects the rail result ("paid","failed","not_attempted", or"outcome_unknown").execution.settlement_statusreflects 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.x402is detectable on verification.
Error handling
| Scenario | Behavior |
|---|---|
| Resource server does not return 402 | Keel does not infer payment is required. Your app must initiate the x402 flow explicitly. |
| Policy denies the payment | HTTP 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 review | execution.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 response | execution.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 dispatch | HTTP 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:
-
Export the permit through Signed Exports.
-
Install
keel-verifier:pip install keel-verifier keel-verify export <export-file> --json -
The verifier recomputes
resource_attributes_canonical_hashfrom the rawresource_attributes_jsonand compares it to the signed value. Any tampering with the x402 payment payload, settlement response, or any other rail evidence is detected aspermit.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.
Related pages
- Payment Rails Overview — all rails at a glance
- Stripe MPP — the other rail with evidence + runtime
- Independent Verification — verify rail evidence with any RFC 8785 implementation
- Execute — the substrate-level execute reference
- Permits — the permit primitive
- x402 Foundation — protocol reference