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 before your response leaves; you cash out with requestWithdraw + withdrawAttested. 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 withdraw 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 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.

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, monotonicity, deposit cap and payTo atomically inside the settle. Steady state is one facilitator call per payment.

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: 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.

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