Add the middleware.
Get paid per request.
Any address you control is a merchant. No registration, no allowlist, nobody to ask. Your 402 names your price; a hub verifies and applies each payment before your response leaves; you cash out with requestWithdraw + withdrawAttested. Charge a tenth of a cent and keep all of it.
import { paymentMiddleware } from "x402-express";
app.use(paymentMiddleware(payTo, { "GET /api/*": "$0.001" }, { url: "https://paychannels.mudit.blog/facilitator" }));That's it.
Read https://paychannels.mudit.blog/MERCHANTS.md and add x402 payments to my API using the Paychannels facilitator at https://paychannels.mudit.blog/facilitator. My merchant address is 0xYOUR_ADDRESS.Your agent reads MERCHANTS.md and wires the route, the cached 402 and the withdraw loop for you.
Price it like an API, not like a checkout.
Cards were built for a $40 basket. At agent prices the fee is the product:
| ticket | card fee share | Paychannels |
|---|---|---|
| $0.001 | 30,000 % | $0 |
| $0.01 | 3,000 % | $0 |
| $0.10 | 303 % | $0 |
| $1 | 33 % | $0 |
A payment costs the payer zero gas and costs the operator 1 on-chain transaction per epoch per hub — one postAttestedRoot (≈112,525 gas) covering every payment that landed, whether a thousand or a hundred million. Cashing out is separate: requestWithdraw then withdrawAttested (≈96,733 gas per account). Set the price to what the request is worth. $0.0001 works.
The hub does the work. Your server does one HTTP call.
Your price, your 402 — built once, cached forever.
Put your own payTo in one /quote call per resource. Every accepts[] entry comes back already stamped with it; serve that body as your 402 with Cache-Control: max-age=3600 and an ETag. There is no rewrite step and nothing to get wrong.
QUOTE=$(curl -sf -X POST "$FACILITATOR_URL/quote" -H 'content-type: application/json' \
-d '{"resource":"https://your.site/api/paid/thing","price":"1000","payTo":"'"$MY_MERCHANT"'"}')
# -> 402-shaped body; every accepts[] entry (exact AND channel alike)
# already carries payTo == $MY_MERCHANT — nothing left to rewritePublish the same entries at GET /.well-known/x402 and an agent pays on its first request — no 402 round trip at all.
The hot path is one call: /settle.
Payment header present → pick the cached entry → settle → serve. No re-quote, no verify pre-flight: the hub re-validates signature, monotonicity, deposit cap and payTo atomically inside the settle. Steady state is one facilitator call per payment.
PAYLOAD=$(base64 -d <<<"$X_PAYMENT_HEADER_VALUE")
SCHEME=$(jq -r .scheme <<<"$PAYLOAD"); PHUB=$(jq -r '.payload.hub // empty' <<<"$PAYLOAD")
# $CACHED_402 is the body you stored/served in (a) for THIS resource:
ENTRY=$(jq -c --arg s "$SCHEME" --arg h "$PHUB" \
'[.accepts[] | select(.scheme==$s)
| select($s=="exact" or ((.extra.contract|ascii_downcase)==($h|ascii_downcase)))][0]' <<<"$CACHED_402")
REQ=$(jq -nc --argjson pl "$PAYLOAD" --argjson e "$ENTRY" \
'{x402Version:2,paymentPayload:$pl,paymentRequirements:$e,method:"GET"}')
S=$(curl -sf -X POST "$FACILITATOR_URL/settle" -H 'content-type: application/json' -d "$REQ")
# success:true -> serve the content, set X-PAYMENT-RESPONSE (envelope below); else map per §3Money moved. Serve now. A retry with the same payment-id replays the same receipt — never re-charged.
An ordinary decline with errorReason. The agent reads it and fixes its side.
Fail closed, no content. The identical retry converges to the original receipt once the rail is back.
Getting your money.
Hands off: a timer on your server
Cash-out is two steps, so your server can automate it: on a cadence, relay a session-key WithdrawAuth to requestWithdraw, then call withdrawAttested with t verifier attestations — using its own gas wallet; the payout can only ever land on your address. Keep a dust threshold and a daily gas budget on the loop (MERCHANTS.md §4).
Hands on: withdraw it yourself
Redemption is permissionless — any funded wallet can submit, the payout goes to your account. One Merkle proof plus t verifier attestations per cash-out, whether one payer or a million paid you. Hourly or daily is plenty.
# your account state, from the explorer's owner-gated account route
# (sign the X-TP-Owner-Auth header with your root key or a registered session key):
curl -sf "$EXPLORER_URL/api/account/$ME?hub=$HUB" -H "X-TP-Owner-Auth: $OWNER_AUTH" \
| jq '{hub, deposited, credit, debit, requestedCum, withdrawnCum, available}'
# requestWithdraw(WithdrawAuth, sig) advances your on-chain requestedCum; the hub reserves
# min(backlog, available) at the next epoch close (available is already net of reservations).
# once an attested root covers the reservation, withdrawAttested(...) pays the delta
# above your on-chain withdrawn cursor with t verifier signatures. Payouts only ever go to
# your account, so anyone may submit.Ask your agent to wire it.
Everything on this page is written twice: once for you, once for a machine. Point any agent with a shell at the raw file and it does the integration end to end.
Read https://paychannels.mudit.blog/MERCHANTS.md and add x402 payments to my API using the Paychannels facilitator at https://paychannels.mudit.blog/facilitator. My merchant address is 0xYOUR_ADDRESS.Read https://paychannels.mudit.blog/PAYING.md and buy one request from my paid route at https://your.site/api/paid/thing, then show me the X-PAYMENT-RESPONSE receipt.MERCHANTS.md · PAYING.md (what your callers send you) · the reference site's manifest · explorer · run your own hub →