x402 Suite · infrastructure

x402-refund-hold

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.

Pay in USDC on Base or USDC on Solana Your client picks the rail
npm install x402-refund-hold

The problem

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.

Pricing

RoutePriceWhat you get back
POST /demo/book$0.01Signed confirmation + captured hold, or a signed refund record — always in the 200 body
GET /holds/:idfreeCurrent ledger state for a hold
POST /verifyfreeSignature check on any record this service issued
GET /healthfreeLiveness + the rails currently advertised

Quickstart

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") });
  }
});

The flow

agent POST /book 402 accepts[2] + X-PAYMENT paywall hold opened 200 confirmed 200 refunded verify → settle handler runs

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.

Both rails, always

RailNetworkpayToFacilitator
EVMbase-sepolia / base0x40252CFDF8B20Ed757D61ff157719F33Ec332402x402.org
Solanasolana / solana-devnetWwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwWPayAI

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.

Docs

Tutorial →

Install, run, take your first 402, pay it, read the artifact, go to mainnet.

API reference →

Every endpoint and every library export, with schemas and error cases.

For AI agents →

Discovery, both rails, MCP integration, and where to get listed.

skill.md →

The agent-facing contract in one file.

.well-known/x402 →

Machine-readable manifest for x402 indexes.

OpenAPI 3.1 →

Including the 402 response and PaymentRequirements schema.