x402
Pay the 402. Keep the figure.
x402 is how a server asks to be paid over HTTP: a 402, a payment, a retry. Bankhaus answers it with a confidential transfer, and puts in the X-PAYMENT header the one thing the server needs: a letter that opens that transfer to the server alone. The server learns the figure it asked for. The chain learns there was a transfer. Nobody learns your balance.
- bankhaus-sealed: a confidential transfer to a handle, the letter as the payload
- bankhaus-mandate: an agent pays from its vault, caps and allowlist enforced first
- The payee verifies offline; no facilitator, no key on the server, no account with us
GET /report/2026-10
402 Payment Required
PAYMENT-REQUIRED bankhaus-sealed · eip155:4663 · 4.20 USDG · bob.bankhaus
confidentialTransfer(bob, Δsender, Δrecipient) no amount in calldata
GET /report/2026-10
X-PAYMENT base64(letter: 4.20 USDG, r, pk_bob, Δrecipient)
200 OK
X-PAYMENT-RESPONSE success · tx 0x7c1e…9b04 · payer gwen.bankhaus
Try one
A 402 you can pay here.
The box on the right is a 402 from a server that wants 4.20 USDG for a report, paid to bob.bankhaus with the sealed scheme. Pay it from your paper account. The X-PAYMENT header appears, and the check under it is exactly what bob's server runs. Then edit the amount in the 402, or a digit in the header, and check again.
- The payee learns the figure it asked for and nothing else about you
- The chain sees a transfer with two deltas and no amount
- The header is a letter: verifiable offline, no key on the server
Metered x402, on paper
Pay an API by the second.
The server answers 402 with a stream instead of a price: 0.05 a second for two minutes. Your agent opens the stream with one proof and retries with the letter that opens the rate to the server. The server checks the letter against the stream the chain holds and serves. The meter runs on chain; the server releases what accrued whenever it likes, and the rate stays sealed.
- One proof per session, not one per request
- The server learns the rate it asked for and nothing about your balance
- Stop paying by cancelling: what accrued is theirs, the rest comes back sealed
Who learns what
The payee
The figure it asked for, from a letter it can check itself, and the transfer reference. Not your balance, not your other payments.
The chain
That one handle paid another, in a transfer carrying two curve points. There is no amount field to read.
Everyone else
An X-PAYMENT header is for one payee: it names them, opens one transfer, and proves nothing to anyone the letter does not name.
The server, live
A paid endpoint runs on this site. Without payment it answers 402 with Bankhaus requirements; with an X-PAYMENT it opens the envelope, verifies the letter offline, then checks on Robinhood Chain that the transfer exists, went to the house and carries exactly that delta, and that it has not paid before.
curl -i https://www.bankhaus.app/api/report HTTP/1.1 402 Payment Required PAYMENT-REQUIRED: eyJ4NDAyVmVyc2lvbiI6MSwiYWNjZXB0cyI6W3sic2NoZW1lIjoiYmFua2hhdXMtc2VhbGVkIi… curl -i -H "X-PAYMENT: $(bankhaus x402 pay 402.json)" https://www.bankhaus.app/api/report HTTP/1.1 200 OK X-PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJ0cmFuc2FjdGlvbiI6IjB4…
import { x402 } from 'bankhaus-sdk'; // any framework, plain data in and out
const need = x402.paymentRequired({ payTo: 'bankhaus', usdg: '0.25', resource: url });
const r = await x402.settle(req.headers['x-payment'], need.body.accepts[0], { viewKey, used });
r.ok && r.onChain ? serve(r.headers) : reply(402, need);The agent
Through bankhaus-mcp, with keys that stay on its machine. The mandate path asks the contract first and never retries a Refused.
x402_requirements(required) // what the 402 asks, in cents, and the invoice id x402_pay(required) // sealed: confidential transfer → X-PAYMENT route(agentId, payee, amount) // or, under a mandate: x402_pay(required, mandate, agentId) // payInvoice from the vault → X-PAYMENT x402_verify(payment, required) // the payee's check, and the response header