The refundable-hold pattern for x402, as drop-in Express middleware. Charge a hold, auto-refund on failure, ledger included — and every outcome comes back as a signed record inside the 200.
npm install x402-refund-hold
x402's exact scheme is pay-then-serve. By the time your handler runs, the USDC has already moved. So what happens when the thing you just sold doesn't materialise — no availability, upstream 500, the flow timed out?
Returning 409 Sorry is not an answer: you took the money. And the caller is an autonomous agent, so it needs proof of the refund in the response, not an email to a human three days later.
refundHold() makes that the default. One line of middleware and every paid request resolves to exactly one signed artifact — a captured hold with your confirmation attached, or a refund record the customer can verify.
| Route | Price | What you get back |
|---|---|---|
POST /demo/book | $0.01 | Signed confirmation + captured hold, or a signed refund record — always in the 200 body |
GET /holds/:id | free | Current ledger state for a hold |
POST /verify | free | Signature check on any record this service issued |
GET /health | free | Liveness + the rails currently advertised |
import express from "express";
import { paywall, refundHold, executorFromEnv } from "x402-refund-hold";
const app = express();
app.use(express.json());
// 1. Dual-rail paywall — the 402 offers Base *and* Solana; the client picks.
app.use(paywall({ "POST /book": "$0.01" }, { service: "my-merchant" }));
// 2. Refundable holds on top.
const holds = refundHold({ executor: executorFromEnv() });
app.post("/book", holds, async (req, res) => {
const booking = await tryToBook(req.body);
if (booking) {
res.json({ outcome: "confirmed", booking, hold: req.hold!.capture({ artifact: booking }) });
} else {
res.json({ outcome: "refunded", refund: await req.hold!.refund("no availability") });
}
});
Both terminal states are 200. The refund path is not an error path — it is the artifact you paid for when the booking could not happen.
| Rail | Network | payTo | Facilitator |
|---|---|---|---|
| EVM | base-sepolia / base | 0x40252CFDF8B20Ed757D61ff157719F33Ec332402 | x402.org |
| Solana | solana / solana-devnet | WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW | PayAI |
Refunds return on the rail the payment arrived on — hold.rail records which one, and createDualRailRefundExecutor routes accordingly. On Solana the facilitator sponsors the network fee, so payers need only USDC and no SOL.
Install, run, take your first 402, pay it, read the artifact, go to mainnet.
Every endpoint and every library export, with schemas and error cases.
Discovery, both rails, MCP integration, and where to get listed.
The agent-facing contract in one file.
Machine-readable manifest for x402 indexes.
Including the 402 response and PaymentRequirements schema.