Docs / Patterns / Hono

Hono.

Server route that creates an invoice and redirects to hosted checkout, and a webhook handler that verifies the signature and reacts to invoice.paid. Both files are paste-and-go.

Install.

$ bun add @settle/sdk hono

Env vars.

Set these on your server. SETTLE_API_KEY is your sk_test_… or sk_live_… key from the dashboard. SETTLE_WEBHOOK_SECRET is returned by register_webhook when you set up the endpoint.

  • SETTLE_API_KEY
  • SETTLE_WEBHOOK_SECRET

Server route.

Receives a POST from your frontend with the order amount and customer email, creates an invoice via the REST API, and 303-redirects the customer to the hosted checkout URL.

src/checkout.ts
// src/checkout.ts
import { Hono } from "hono";

export const checkout = new Hono();

checkout.post("/api/checkout", async (c) => {
  const body = await c.req.json();
  const res = await fetch("https://api.settle.xxx/v1/invoices", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.SETTLE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      amount_usd: body.amount_usd,
      customer_email: body.customer_email,
      metadata: body.metadata,
      return_url: `${new URL(c.req.url).origin}/orders/success`,
      cancel_url: `${new URL(c.req.url).origin}/cart`,
    }),
  });
  if (!res.ok) return c.json({ error: "checkout_failed" }, 502);
  const invoice = await res.json();
  return c.redirect(invoice.checkout_url, 303);
});

Webhook handler.

Verifies the HMAC-SHA256 signature on the raw request body, then handles invoice.paid. Read Webhooks for the event list and retry semantics.

src/webhooks.ts
// src/webhooks.ts
import { Hono } from "hono";
import { createHmac, timingSafeEqual } from "node:crypto";

export const webhooks = new Hono();

webhooks.post("/webhooks/settle", async (c) => {
  const signature = c.req.header("settle-signature");
  if (!signature) return c.text("missing signature", 400);

  const raw = await c.req.text();
  const expected = createHmac("sha256", process.env.SETTLE_WEBHOOK_SECRET!)
    .update(raw)
    .digest("hex");

  const a = Buffer.from(signature, "hex");
  const b = Buffer.from(expected, "hex");
  if (a.length !== b.length || !timingSafeEqual(a, b)) {
    return c.text("bad signature", 401);
  }

  const event = JSON.parse(raw);
  if (event.type === "invoice.paid") {
    // Mark order paid.
  }
  return c.json({ received: true });
});

Notes.

Mount both routers on your Hono app: app.route('/', checkout); app.route('/', webhooks). Hono gives you raw body via c.req.text() — use that for signature verification, not c.req.json().

Was this helpful?