Webhooks and verification
Receive signed payment events, then confirm current payment state before fulfilling an order.
On this page
Register an endpoint
Open Developers, add an HTTPS endpoint and choose events. A merchant endpoint can cover one store or all its stores.
Saving an endpoint, revealing its secret or rotating that secret requires a fresh sign-in. Keep the whsec_ secret on your server.
Verify the unmodified request body
Read the raw body before JSON parsing. verifyWebhook checks webhook-id, webhook-timestamp and webhook-signature with your signing secret.
The signature is v1, followed by base64 HMAC-SHA256 of id.timestamp.body. The key is the base64 secret after whsec_. A timestamp more than five minutes away is rejected.
import { verifyWebhook } from "forwend";
export async function POST(request: Request) {
const payload = await request.text();
let event;
try {
event = verifyWebhook({
payload,
headers: request.headers,
secrets: [process.env.FORWEND_WEBHOOK_SECRET!],
});
} catch {
return new Response("Invalid webhook", { status: 400 });
}
// Persist event.id in your durable job queue before acknowledging.
// Deduplicate by event.id; read canonical API state before fulfillment.
// This example verifies only. Add your queue before using it for orders.
return Response.json({ verifiedEventId: event.id });
}Acknowledge safely
- The example verifies a signature only. Add durable queuing and fulfillment checks before using it to process orders.
- Store event.id with a uniqueness constraint. A retry is the same event, not another payment.
- Compare sequence for each invoice. Do not apply an older event over newer state.
- The invoice reflects its state when the first delivery body is produced. Retries retain those bytes. Fetch canonical state before fulfilling.
- Acknowledge with a 2xx response within 15 seconds, after persisting the event for processing.
Retries and secret rotation
A failed response, timeout or redirect is retried after 5 seconds, 5 minutes, 30 minutes, 2 hours and 5 hours, then every 10 hours: 12 tries over about three days.
HTTP 410 or the final failed attempt disables the endpoint. Inspect deliveries and re-enable it in Developers after fixing the cause.
A rotated secret overlaps for 24 hours. During overlap the old and new signatures are both sent. Update your verifier before removing the old secret.