Accept payments
This guide adds per-request pricing to a Hono app, such as a Cloudflare Worker, with radius-sdk. Buyers pay in SBC over x402, and the Radius facilitator settles each payment on Radius before your handler runs. Your server needs a recipient address, not a private key.
Before you start
- A Hono app. The middleware does no I/O at module scope, so it runs on Cloudflare Workers.
- A wallet address to receive payments. See Create and fund a wallet.
- To test payments, a second wallet funded with testnet SBC.
Install
pnpm add radius-sdk honoProtect routes
Add radiusPayments in front of the routes you want to charge for. This Worker charges 0.001 SBC for GET /api/lookup and 0.01 SBC for POST /api/query, and pays both to the PAY_TO binding:
import { Hono } from 'hono';
import { radiusPayments, type RadiusPaymentVariables } from 'radius-sdk/hono';
type Env = { Bindings: { PAY_TO: `0x${string}` }; Variables: RadiusPaymentVariables };
const app = new Hono<Env>();
app.use(
'/api/*',
radiusPayments<Env>({
network: 'testnet',
payTo: (c) => c.env.PAY_TO,
routes: {
'GET /api/lookup': { price: '0.001 SBC', description: 'One lookup' },
'POST /api/query': '0.01 SBC',
},
}),
);
app.get('/api/lookup', (c) => c.json({ result: 'ok' }));
app.post('/api/query', (c) => c.json({ results: [] }));
export default app;Route keys are METHOD /path and accept Hono-style * wildcards. Requests that match no route pass through for free.
Set prices
A price is a string in SBC display units ('0.001 SBC' or '0.001') or an object in base units ({ amount: '1000' }; SBC has six decimals). To price per request, pass a function of the Hono context:
routes: {
'GET /api/report': { price: (c) => (c.req.query('detail') === 'full' ? '0.05 SBC' : '0.01 SBC') },
},A route can also override the recipient with its own payTo.
Test on testnet
Run the app locally, for example with wrangler dev, and request a protected route without paying:
curl -i http://localhost:8787/api/lookupThe response is 402 Payment Required with a PAYMENT-REQUIRED header describing the price. Pay it from a funded testnet wallet with radius-cli:
RADIUS_HOME=.radius RADIUS_NETWORK=testnet \
radius-cli wallet x402 get http://localhost:8787/api/lookup \
--x402-threshold 0.01 \
-yThe CLI prints the response and the settlement transaction. A payer that holds only SBC can pay: the Radius facilitator sponsors the one-time Permit2 approval.
Read the payment in a handler
For a paid request, c.get('radiusPayment') holds the receipt: transaction, payer, amount, and explorerUrl. To log every payment in one place, pass onSettled:
radiusPayments<Env>({
// …
onSettled: (receipt, c) => console.log('paid', c.req.path, receipt.payer, receipt.transaction),
});The middleware also adds a PAYMENT-RESPONSE header carrying the transaction hash, so the buyer gets a receipt too.
Choose when payment settles
By default, the middleware verifies and settles the payment before your handler runs, so the handler only runs for money already received. If the handler then fails, the buyer has paid without getting the resource. Log the transaction hash with the failure so you can refund or serve the resource on retry.
Set settle: 'after' to verify the payment, run the handler, and settle only when it responds with a status below 400. This is the x402 default flow: buyers are not charged for an error response, but your handler does its work before the payment is final.
Go to mainnet
Set network: 'mainnet', or remove the option, since mainnet is the default. Point payTo at your mainnet recipient. The SBC address and the Radius facilitator switch with the network.
To use a different facilitator, pass facilitator. See Facilitator in the reference.
Next steps
- radius-sdk reference — Every option for
radiusPayments - Make payments — Pay x402 endpoints from an app or agent
- x402 integration — How x402 payments work on Radius
worker-sellerexample — A runnable Worker with free and paid routes