BuildWeb3
x402 Payments
x402 defines HTTP exchanges for per-request machine payments. Chio decides which agent may pay and for what, then records a signed receipt.
Two different ACPs, don't confuse them
The acronym ACP appears twice in the agent ecosystem and they are unrelated protocols:
- Agent Client Protocol is the IDE/editor integration surface used by coding agents to discover and invoke tools. Covered in Chio's Wrap an ACP Server guide.
- Agentic Commerce Protocol is a family of specifications for machine-to-machine payments; x402 is the HTTP-layer member. This page is about that one.
How x402 Works on the Wire
An x402 exchange uses two requests. The first request arrives without payment; the server responds 402 Payment Required with a structured body that names the price, the accepted settlement rail, and the recipient. The client forms a payment, signs it, and retries with an X-PAYMENT header. The server validates the payment proof, completes the request, and returns the response plus a settlement identifier. This is the x402 specification, quoted so the rest of the page has something to point at; Chio implements neither end of the exchange itself.
# First request, no payment
GET /expensive-resource HTTP/1.1
Host: api.example.com
HTTP/1.1 402 Payment Required
Content-Type: application/json
{
"x402": "0.5",
"price": { "amount": "0.01", "asset": "USDC" },
"pay_to": "0xA11CE...",
"networks": ["base-mainnet"],
"nonce": "b41f-...-c8a9"
}
# Second request, with payment proof
GET /expensive-resource HTTP/1.1
Host: api.example.com
X-PAYMENT: base64url(<signed payment envelope>)
HTTP/1.1 200 OK
Content-Type: application/json
X-PAYMENT-RESPONSE: base64url(<settlement receipt>)
{ "data": "..." }Chio's validation code currently supports x402 spec version 0.5. A bound x402 claim whose source_protocol_version is anything other than 0.5 is rejected as an unsupported source version, and a claim carrying a refunded status is a hard claim failure.
What Chio Adds
x402 is a payment protocol. It does not define which agent is allowed to pay, how much in aggregate, against what policy, or what receipt should be kept. Chio sits on both sides of the exchange and adds the following controls:
- Capability scoping. An agent must hold a capability whose scope grants the tool that spends. A grant names a
server_id, atool_nameand a list ofoperations(invokefor a payment call), plus the optionalmax_invocations,max_cost_per_invocationandmax_total_costceilings. There is no dotted permission namespace to grant. Without a matching grant the kernel denies before the payment is ever constructed, so no paid request leaves the box without authority. See Capabilities for the token shape. - Pre-authorization and budgets. Chio evaluates the upcoming payment against per-call and cumulative budgets from the Budgets & Metering controls. A call that exceeds a limit is denied before payment is sent.
- A receipt that carries the payment. The payment is not a second record beside the authorization. The same signed receipt that records the verdict carries the money outcome on
metadata.financial, so a receipt log associates a payment with the capability that permitted it without a join. - Revocation. A capability can be revoked mid-flight. Once revoked, the kernel refuses any further x402 payment even if the agent process still holds the token.
- Oracle-aware pricing. When the settlement currency differs from the budget currency, Chio consults Chainlink or Pyth to price the call, applies the configured FX margin, and attaches the evidence to the receipt.
Governed Flow
Chio governs the paying side. The kernel decides whether the agent may spend, calls the payment adapter for one external authorization, and signs a receipt that carries the outcome. The x402 endpoint and its facilitator stay where they are; Chio does not sit in front of them.
What the inbound half actually is
Nothing in Chio parses an X-PAYMENT header or builds a 402 Payment Required response; the wire exchange above is the x402 specification, not a Chio surface. What ships for the receiving direction is an offline claim validator: an external.x402.payment.v1 subject inside a proof envelope is checked for a matching source version, four SHA-256 digests, a transaction passport reference that matches the envelope, an order id, network, asset, a non-zero amount, a status of authorized or settled, and a payment receipt reference bound into the envelope.
Integration Shape
The caller never assembles a payment envelope. Authorization, budget reservation, adapter dispatch, and receipt signing all happen inside the kernel, so the integration surface is the same one every other tool call uses: a capability, an intent, and a verdict. The payment-specific parts live in two Rust places, and neither of them is on the caller's side of the boundary: the four chio-settle::payments functions described under Governed interoperability API, and chio-kernel::payment::X402PaymentAdapter.
pub struct X402PaymentAdapter {
base_url: String,
authorize_path: String,
bearer_token: Option<String>,
http: ureq::Agent,
}One path, not four. The adapter makes exactly one remote call, the authorization, and treats capture, release and refund as prepaid bookkeeping: those three synthesize a result locally and send no HTTP at all. That is deliberate for per-request settlement, where the money moves once and the later transitions are records rather than instructions. If you need every transition confirmed by an external facilitator, the adapter for that is a different type, AcpPaymentAdapter, which carries a path per operation:
pub struct AcpPaymentAdapter {
base_url: String,
authorize_path: String,
capture_path: String,
release_path: String,
refund_path: String,
settlement_state_path: String,
bearer_token: Option<String>,
http: ureq::Agent,
}That makes the decision path runnable without a chain, a key, or a facilitator. The CLI builds an ephemeral kernel, wires a deterministic no-broadcast payment adapter, and puts one governed MustPrepay call through it. This is the no-key lane CI runs, and it is the fastest way to see what a governed payment records.
A governed prepayment, allowed
$ chio mcp governed-sim \
--payment-adapter sim \
--governed-mustprepay \
--out ./receipt.jsonThe command exits 0 and writes one signed receipt. It is the same call examples/governed-x402-sim/smoke.sh runs. The payment is not a second record beside the authorization; it is a branch of the same receipt's cost breakdown, under metadata.financial.cost_breakdown.payment, carrying the adapter that moved the money, the authorization id it returned, and the governed intent the approval was bound to. The money outcome sits beside it on metadata.financial: cost_charged, budget_remaining, and settlement_status.
$ jq '{id, decision, receipt_kind, financial: .metadata.financial}' ./receipt.json{
"id": "05cb8d13de28bf96c7220d74f45c1563f4d9f36fc8b0ac672f8d7c7ff9337a25",
"decision": {
"verdict": "allow"
},
"receipt_kind": "mediated_decision",
"financial": {
"budget_remaining": 900,
"budget_total": 1000,
"cost_breakdown": {
"payment": {
"adapter_metadata": {
"adapter": "sim",
"commerce": null,
"governed": {
"approvalTokenId": "governed-sim-approval-1",
"intentHash": "1ee415c06b0bb092d5c3fec07097fb41d192ee06d43649f404b543d31dd22f18",
"intentId": "governed-sim-intent-1",
"purpose": "no-key CI smoke",
"serverId": "governed-sim-srv",
"toolName": "compute"
},
"mode": "prepaid_no_broadcast"
},
"authorization_id": "sim-268afdc9129342029708eb795c87da08",
"preauthorized_units": 100,
"recorded_units": 100
}
},
"cost_charged": 100,
"currency": "USD",
"delegation_depth": 0,
"grant_index": 0,
"payment_reference": "sim-268afdc9129342029708eb795c87da08",
"root_budget_holder": "66c2cd97052842de29bda51688fcb33c87f6a74bd90a6c4546fe03b7479271b5",
"settlement_status": "settled"
}
}The sim adapter is deterministic in what it broadcasts, so authorization_id repeats across runs. The kernel keypair is ephemeral, so id, intentHash, and root_budget_holder differ on every invocation.
The same intent with no payment adapter
An intent that mandates prepayment cannot fall back to running unpaid. Take the adapter away and the identical call is refused before execution, with the budget untouched.
$ chio mcp governed-sim \
--payment-adapter none \
--governed-mustprepay \
--out ./deny.jsonerror [urn:chio:error:cli:other]: governed MustPrepay denied: governed transaction denied: governed intent mandates prepayment (settlement_mode=MustPrepay) but no payment adapter is configured
context: {"domain":"cli","severity":"error","stability":"deprecated","string_code":"CHIO-CLI-OTHER"}
suggested fix: Preserve the original message and migrate the call site to a specific registry code when touched.The nonzero exit does not skip the record: --out is still written, and the denial is still a signed receipt. It records what the call would have cost on attempted_cost, that nothing was charged on cost_charged, and that the budget is whole because budget_remaining still equals budget_total. There is no cost_breakdown at all, because no adapter ever ran.
$ jq '{id, decision, financial: .metadata.financial}' ./deny.json{
"id": "fa761f9eb9d61ede8b6a3e320471e50c9d1eb4e4e234ede0ffd0079ae291e67d",
"decision": {
"verdict": "deny",
"reason": "governed transaction denied: governed intent mandates prepayment (settlement_mode=MustPrepay) but no payment adapter is configured",
"guard": "kernel"
},
"financial": {
"attempted_cost": 100,
"budget_remaining": 1000,
"budget_total": 1000,
"cost_charged": 0,
"currency": "USD",
"delegation_depth": 0,
"grant_index": 0,
"root_budget_holder": "d982de219319344a1f643e7fa1eda75e91cb47f1324ae9b4e45a7c1cf0eb23db",
"settlement_status": "not_applicable"
}
}Governed Interoperability API
x402 is not its own crate. It is one of four bounded payment-interop capabilities that ship inside chio-settle, all of which sit on top of governed dispatch and settlement truth and none of which replace signed receipts. The x402 projection is the one this page covers; the other three round out the machine-payment and gas-abstraction compatibility surface:
- x402 payment-requirement projection ·
build_x402_payment_requirementsturns one governed settlement dispatch into an x402 payment-requirement object, bound to a facilitator URL, resource identifier, and an explicit accepted-token list. - EIP-3009 gasless transfer ·
prepare_transfer_with_authorizationprepares onetransferWithAuthorizationdigest for review, with nonce dedup enforced through anEip3009NonceStoreso a nonce cannot be replayed. - Circle nanopayment evaluation ·
evaluate_circle_nanopaymentevaluates one nanopayment candidate, and only when operator-managed custody is declared explicitly. - ERC-4337 paymaster compatibility ·
prepare_paymaster_compatibilityevaluates one paymaster compatibility record, bounded by explicit reimbursement ceilings.
The boundary is deliberately narrow. The interop layer is not a generic payment-facilitator marketplace, does not perform implicit custody handoff, and does not offer universal gas sponsorship. It never mutates signed Chio receipts to reflect off-protocol facilitator state: the shipped code supports interoperability only.
Coverage
Two things ship, and they are not symmetric. Outbound, the kernel authorizes the spend and the X402PaymentAdapter makes one remote authorization call before execution. Inbound, an external.x402.payment.v1 claim inside a proof envelope is validated offline against its digests and its bound payment receipt. There is no Chio component that terminates an x402 HTTP exchange. Either way the capability that authorized the spend and the money outcome land on the same signed receipt, so a payment and the grant that permitted it are one record rather than two systems to reconcile.
Next Steps
- Settlement · the on-chain layer where x402 payments actually land
- Chainlink Oracles · cross-currency pricing for x402 quotes outside the budget currency
- Budgets & Metering · per-agent spend envelopes that gate x402 calls
- Capabilities · the token format that authorises each payment