Docs / Patterns / Next.js
Next.js.
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.
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_KEYSETTLE_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.
// app/api/checkout/route.ts
// POST { amount_usd, customer_email?, metadata? } -> 303 redirect to Settle hosted checkout.
import { NextResponse } from "next/server";
export async function POST(req: Request) {
const body = await 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: `${req.headers.get("origin")}/orders/success`,
cancel_url: `${req.headers.get("origin")}/cart`,
}),
});
if (!res.ok) {
return NextResponse.json({ error: "checkout_failed" }, { status: 502 });
}
const invoice = await res.json();
return NextResponse.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.
// app/api/webhooks/settle/route.ts
import { NextResponse } from "next/server";
import { createHmac, timingSafeEqual } from "node:crypto";
export async function POST(req: Request) {
const signature = req.headers.get("settle-signature");
if (!signature) return new NextResponse("missing signature", { status: 400 });
const raw = await req.text();
const expected = createHmac("sha256", process.env.SETTLE_WEBHOOK_SECRET!)
.update(raw)
.digest("hex");
const sigBuf = Buffer.from(signature, "hex");
const expBuf = Buffer.from(expected, "hex");
if (sigBuf.length !== expBuf.length || !timingSafeEqual(sigBuf, expBuf)) {
return new NextResponse("bad signature", { status: 401 });
}
const event = JSON.parse(raw);
if (event.type === "invoice.paid") {
// Mark order paid. event.data.invoice has id, amount_usd, payment_tx, metadata.
console.log("paid", event.data.invoice.id);
}
return NextResponse.json({ received: true });
}
Notes.
Place the route at app/api/checkout/route.ts and the webhook at app/api/webhooks/settle/route.ts. Use the test key (sk_test_…) until you flip the merchant to live. The webhook handler must read the raw request body before parsing — Next's App Router gives you that via req.text().