On this page
- @fianto/hono 0.3.0 exports checkout() and webhooks(), lowercase, for hono 4 or later.
- On Workers there is no process.env: pass new Fianto({ appId, appSecret }) and the webhook secret from c.env, inside the route.
- Fianto CI tests the SDK on Node.js 22 and 24; Workers, Bun and Deno are designed for but not tested yet.
Which runtimes can run Fianto with Hono?
Fianto's Hono handlers are written for any runtime with the Fetch API, but only Node is tested. The Fianto docs put it plainly: "CI tests the SDK on Node.js 22 and 24 and smoke-tests the built @fianto/sdk on Node.js 20.3.0; Workers, Bun and Deno are not tested yet." (Fianto docs: Hono, read 15 October 2026.) Plan for that: run one real payment end to end on the runtime you deploy to before you take customers there.
| Node | Cloudflare Workers | Bun | |
|---|---|---|---|
| Where secrets come from | process., loaded with node --env-file=.env (Node 20.6+) | c. bindings, only inside a request | process.; Bun reads .env files itself |
| How the app starts | @hono/node-server serve() | export default app | export default app |
| Where to build the handlers | Module level | Inside each route | Module level |
| Tested in Fianto CI (15 Oct 2026) | Yes, Node 22 and 24 | No | No |
One detail from the SDK source shows Workers was considered in the code, not only in the docs. The SDK sends every API request with redirect: 'manual', because its comments note that redirect: 'error' throws while the request is being built on workerd, the Workers runtime, which would fail every call there. The same comments record how workerd passes a redirect response through. Workers has been looked at closely; it is just not in the test matrix.
How do you add USDC checkout to a Hono app on Node?
On Node you add USDC checkout with two app. routes and @hono/node-server to listen. Install @fianto/hono, @fianto/sdk and hono; the packages need Node 20.3 or later. The handlers read FIANTO_, FIANTO_ and FIANTO_ from the environment. Price and order_ are decided here, on your server, from your own data; the browser only says which item.
import { serve } from '@hono/node-server';
import { Hono } from 'hono';
import { checkout, webhooks } from '@fianto/hono';
import { orders } from './orders.js'; // your own data layer
const ITEMS: Record<string, { amount: string; description: string }> = {
report: { amount: '25.00', description: 'Quarterly market report (PDF)' },
};
const app = new Hono();
app.post(
'/api/checkout',
checkout({
createSession: async (request) => {
const body = (await request.json().catch(() => null)) as { item?: string } | null;
const item = body?.item === undefined ? undefined : ITEMS[body.item];
if (item === undefined) return new Response('Unknown item', { status: 400 });
const order = await orders.open(item); // your row, with its own id
return {
mode: 'payment',
order_id: order.id,
amount: item.amount,
description: item.description,
success_url: 'https://reports.example/orders/confirming',
cancel_url: 'https://reports.example/',
};
},
onError: (error) => console.error('fianto checkout failed', error),
}),
);
app.post(
'/webhooks/fianto',
webhooks({
secret: process.env.FIANTO_WEBHOOK_SECRET,
onEvent: async (event) => {
if (event.type === 'order.paid') {
// One transaction: record event.id (skip if seen), mark the order paid, deliver.
await orders.markPaid(event.id, event.data.order_id);
}
},
onVerificationError: (error) => console.warn('fianto webhook rejected:', error.reason),
}),
);
serve({ fetch: app.fetch, port: 3000 });Keep body-reading middleware off both routes. Hono hands the SDK c., the original Request, and the webhook signature is checked over its exact bytes. Fulfil from order. only: the checkout page shows the payer success at confirmed, but Fianto marks the order PAID and sends order. only at finalized. What those two words mean on Solana is in Solana payment finality.
How do you accept USDC on Cloudflare Workers with Hono?
On Cloudflare Workers you accept USDC with the same handlers, built inside each route so they can read c.. Hono's Workers guide says you can bind environment values and "access them in c." (Hono: Cloudflare Workers, read 15 October 2026). Workers has no process., so the default new Fianto() finds no credentials. A handler created at module level runs before any binding exists, which is why the factory call moves inside the route.
import { Hono } from 'hono';
import { Fianto } from '@fianto/sdk';
import { checkout, webhooks } from '@fianto/hono';
interface Bindings {
FIANTO_APP_ID: string;
FIANTO_APP_SECRET: string;
FIANTO_WEBHOOK_SECRET: string;
}
const app = new Hono<{ Bindings: Bindings }>();
app.post('/api/checkout', (c) =>
checkout<{ Bindings: Bindings }>({
fianto: new Fianto({ appId: c.env.FIANTO_APP_ID, appSecret: c.env.FIANTO_APP_SECRET }),
createSession: async () => ({
mode: 'payment',
order_id: `report_${crypto.randomUUID()}`, // better: your own order's id
amount: '25.00',
description: 'Quarterly market report (PDF)',
success_url: 'https://reports.example/orders/confirming',
cancel_url: 'https://reports.example/',
}),
onError: (error) => console.error('fianto checkout failed', error),
})(c),
);
app.post('/webhooks/fianto', (c) =>
webhooks({
secret: c.env.FIANTO_WEBHOOK_SECRET,
onEvent: async (event) => {
if (event.type === 'order.paid') {
console.log('paid', event.data.order_id, event.id); // write to D1 or KV here
}
},
})(c),
);
export default app;Set the three values as Worker secrets for production, and in a . file for local runs, which Hono's guide describes. Two failures point straight at a missed binding. The webhook route answers 400 invalid_ with reason invalid_ when no secret reached it. The checkout route answers 500, and onError logs "Missing appId: pass it to new Fianto({ appId }) or set FIANTO_APP_ID." For a self-hosted or local Fianto backend, also pass baseUrl to new Fianto().
Keys, sessions, signed webhooks and the CLI, in one place.
See the developer overviewDoes the Hono code change on Bun?
On Bun the Node version of the app needs only a different start line. Bun's docs say it "reads your . files automatically" and that Bun. and import. are "aliases of process." (Bun: environment variables, read 15 October 2026), so the default new Fianto() and process. find their values. Drop @hono/node-server and end the file with export default app, which Hono's Bun guide uses. Bun is not in Fianto's CI, so treat your first real payment there as the test.
What do the Fianto Hono handlers do for you?
The two handlers carry the parts of a payment integration that are easy to get wrong by hand. An AI coding agent given the Fianto agent skill builds the same two routes with @fianto/hono. Each line below is behaviour in the SDK source, not a recommendation.
- Same-site check. checkout() accepts a POST only with Sec-Fetch-Site: same-origin, or an Origin in allowedOrigins (by default the request URL's own origin). Anything else is 403 forbidden_origin.
- Popup sessions. It always creates the session with ui_mode popup and answers { id, url } for the pay button.
- Double clicks. When the order_id already has an open session with the same terms, it reissues that session's link instead of failing. Different terms are refused with 409 order_session_mismatch.
- Safe errors. Only an allowlisted set of error codes reaches the browser; everything else, wrong credentials included, is a generic 500 internal_error, with the original passed to onError.
- Verification. webhooks() checks headers and timestamp (300 s tolerance by default) before reading the body, caps the body at 1 MiB, and answers the endpoint.verification challenge itself.
- Retries. A callback that throws makes the route answer 500, so Fianto delivers the event again.
What the handlers cannot do is make your callback idempotent. Delivery is at least once and unordered, so record event. in the same transaction as the fulfilment and skip ids you have seen. The signature scheme itself, and how to check it without the SDK, is covered in verifying Standard Webhooks signatures.
What does a one-off USDC payment cost through Hono?
A one-off USDC payment costs the merchant nothing, whichever runtime serves the route. The amount you return from createSession is what lands in your wallet, in the payer's one transaction, with nobody holding it in between. During the beta there is no Fianto fee; after it, the payer pays about 0.1 to 0.5% on top. One-off payments carry no USDC network fee and need no account rent. The payer signs one Solana transaction with a fee under $0.01, and the checkout tells them the exact SOL their wallet needs.
The amount is a decimal string from 0.01 to 1,000,000 USDC with at most 6 decimals, so '25. works and a JSON number does not. Your receiving wallet must already hold a USDC token account, or creating a session fails with 422 merchant_. Sending that wallet any amount of USDC once creates it. The wider picture, from wallet to first payment, is in how to accept USDC on Solana.
How do you check the Hono webhook route before a real payment?
You check the Hono webhook route by sending it a signed sample from the Fianto CLI, which makes no API call and moves no money.
- Export the secret in the shell that runs the CLI:
export FIANTO_. The CLI does not read .env files.WEBHOOK_ SECRET=whsec_ . . . - Start the app: port 3000 in the Node example, 8787 for the Workers dev server that Hono's guide uses.
- Run
npx @fianto/cli trigger order.. Without --forward-to, nothing is sent.paid --forward-to http://localhost:3000/webhooks/fianto - Expect "→ 200 order.paid (local sample)" in the terminal, and your onEvent branch to run for the sample order. Each run is a new event id.
There is no test mode or sandbox: Fianto runs on Solana mainnet. Once samples pass, make one real 0.01 USDC payment on the runtime you deploy. The full routine is in testing a USDC integration without a test mode, and the same app written for Next.js is accepting USDC in a Next.js app.