Apps
Hono SDK
The @payweave/hono package works across every Hono runtime — Cloudflare Workers, Bun, Deno, Node.js. The recommended entry point is payweave.route(), which bundles validation, charging, auto-refund, and marketplace registration.
Installation
Terminal
npm install @payweave/hono zodFull example
TypeScript
import { Hono } from 'hono';
import { Payweave } from '@payweave/hono';
import { z } from 'zod';
const app = new Hono();
// Cloudflare Workers: use fromEnv because env is only available at request time.
const payweave = Payweave.fromEnv(env => ({
appId: env.PAYWEAVE_APP_ID,
appSecret: env.PAYWEAVE_APP_SECRET,
baseUrl: env.PAYWEAVE_API_URL,
}));
// Node / Bun / Deno: pass directly.
// const payweave = new Payweave(process.env.PAYWEAVE_APP_ID!, process.env.PAYWEAVE_APP_SECRET!);
const WeatherInput = z.object({ city: z.string() });
payweave.route(app, 'post', '/api/weather', {
price: '0.001',
description: 'Current weather for a city',
inputSchema: WeatherInput,
handler: async (_c, body) => {
// body: { city: string } — typed from Zod
const res = await fetch(`https://api.weather/?q=${body.city}`);
if (!res.ok) throw new Error('weather_upstream_error');
return await res.json();
},
});
payweave.mountSync(app);
export default app;Dynamic pricing
The price option accepts a function that receives the Hono context. Useful for billing based on input size.
TypeScript
payweave.route(app, 'post', '/api/summarize', {
price: async (c) => {
const body = await c.req.json();
return body.text.length > 5000 ? '0.01' : '0.002';
},
description: 'Text summarization',
inputSchema: z.object({ text: z.string() }),
handler: async (_c, body) => summarize(body.text),
});Accessing payment metadata
After settlement, the charge middleware writes payment data onto the context. Handlers can read it via c.get(...):
TypeScript
handler: async (c, body) => {
const txHash = c.get('payweaveTransactionHash'); // on-chain transaction hash
const payer = c.get('payweavePayer'); // payer wallet address
const receipt = c.get('payweaveReceipt'); // serialized Payment-Receipt
return { txHash, payer };
}Manual refund
For fine-grained control (e.g. different HTTP status per error kind), disable auto-refund and call payweave.refund() directly.
TypeScript
payweave.route(app, 'post', '/api/weather', {
price: '0.001',
refundOnError: false, // opt out of auto-refund
inputSchema: WeatherInput,
handler: async (c, body) => {
try {
return await fetchWeather(body.city);
} catch (err: any) {
if (err.status === 429) {
await payweave.refund(c.env, {
transactionHash: c.get('payweaveTransactionHash')!,
reason: 'upstream_rate_limited',
});
return c.json({ error: 'rate_limited', retry_after: 60 }, 429);
}
throw err; // propagates — framework handles
}
},
});Low-level: payweave.charge()
For routes outside the route() helper (custom flows, streaming), use charge() as bare middleware. These routes aren't registered in the marketplace.
TypeScript
app.get(
'/api/custom',
payweave.charge({ price: '0.001', description: 'Custom' }),
(c) => c.json({ ok: true })
);Call
payweave.mountSync(app) once after all routes are registered. It mounts GET /_payweave/sync, which the dashboard's Sync endpoints button pings to publish your manifest.