Paychannels
05 · Merchants

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.

your server
import { paymentMiddleware } from "x402-express";

app.use(paymentMiddleware(payTo, { "GET /api/*": "$0.001" }, { url: "https://paychannels.mudit.blog/facilitator" }));

That's it.

or paste into your agent
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.

01

Price it like an API, not like a checkout.

Cards were built for a $40 basket. At agent prices the fee is the product:

ticketcard fee sharePaychannels
$0.00130,000 %$0
$0.013,000 %$0
$0.10303 %$0
$133 %$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.

02

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.

bash · MERCHANTS.md §2 (a)
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 rewrite

Publish the same entries at GET /.well-known/x402 and an agent pays on its first request — no 402 round trip at all.

03

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.

bash · MERCHANTS.md §2 (b)
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 §3
success:true

Money moved. Serve now. A retry with the same payment-id replays the same receipt — never re-charged.

success:false → 402

An ordinary decline with errorReason. The agent reads it and fixes its side.

unreachable → 503

Fail closed, no content. The identical retry converges to the original receipt once the rail is back.

04

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.

bash · MERCHANTS.md §4
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.

bash · cast · MERCHANTS.md §4
# 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 regardless
05

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.

paste into your agent
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.
then test it from a second agent
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.