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 in 32.3 µs; you cash out with one claim per hub. 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 claim 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 you 2 on-chain transactions per epoch per hub — one postEpochRoot (≈74,000 gas, the operator's) and one claimEpoch (≈90,000 gas, payer-count independent) — whether a thousand payments landed or a hundred million. 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, epoch, monotonicity, deposit cap and payTo atomically inside the settle. Steady state is one facilitator call per payment, answered from a hub in microseconds.
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: hosted auto-claim
Opt in once. The claim runner submits claimEpoch on a cadence with its own gas; the payout can only ever land on your address.
curl -s -X POST "$CLAIM_RUNNER_URL/optin" -H 'content-type: application/json' \
-d '{"merchant":"'"$ME"'"}'Hands on: claim it yourself
Claims are permissionless — any funded wallet can submit, the leaf names you. One Merkle proof per hub, ~90,000 gas whether one payer or a million paid you. Hourly or daily is plenty.
# addresses from /meta or deployments/amoy.json's last entry:
LATEST=$(cast call "$HUB" 'latestEpochId()(uint64)' --rpc-url "$RPC_URL")
DELAY=$(cast call "$HUB" 'epochRootDelay()(uint256)' --rpc-url "$RPC_URL")
# pick the newest id with epochRootPostedAt(id)+DELAY <= now (scan LATEST downward)
P=$(curl -sf "$ENGINE_URL/epoch/proof?merchant=$ME&epoch=$ID") # 404 -> nothing accrued this hub/epoch
CUM=$(jq -r .cumulative <<<"$P")
[ "$(jq -r .root <<<"$P")" = "$(cast call "$HUB" 'epochRoot(uint64)(bytes32)' "$ID" --rpc-url "$RPC_URL")" ] || exit 1 # never submit on mismatch
ALREADY=$(cast call "$HUB" 'epochClaimed(address)(uint256)' "$ME" --rpc-url "$RPC_URL") # cumulative AMOUNT cursor, not an epoch counter
PROOF="[$(jq -r '.proof | join(",")' <<<"$P")]"
cast send "$HUB" 'claimEpoch(uint64,address,uint256,bytes32[])' "$ID" "$ME" "$CUM" "$PROOF" \
--private-key "$(cat .wallets/gas.key)" --rpc-url "$RPC_URL" # any gas wallet; payout goes to $ME regardlessAsk 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 →