Skip to Content
GuidesWebhook Processing

Webhook Processing

Webhooks let you run server-side logic whenever a form submission arrives: save to a database, send a Slack notification, trigger an email sequence, or anything else.

Formtorch sends a POST request to your endpoint with a JSON body and an HMAC-SHA256 signature header.

Add your webhook endpoint in the dashboard

Go to Form SettingsWebhooksAdd endpoint. Enter your URL, for example https://yourapp.com/api/webhooks/formtorch. Copy the webhook secret.

Store the secret

.env.local
FORMTORCH_WEBHOOK_SECRET=whsec_your_secret_here

Create the route handler

app/api/webhooks/formtorch/route.ts
import { createHmac } from "crypto"; export async function POST(req: Request) { const body = await req.text(); const signature = req.headers.get("x-formtorch-signature") ?? ""; // Verify the signature const expected = `sha256=${createHmac( "sha256", process.env.FORMTORCH_WEBHOOK_SECRET! ) .update(body) .digest("hex")}`; if (signature !== expected) { return new Response("Unauthorized", { status: 401 }); } // Parse the payload const event = JSON.parse(body) as FormtorchEvent; // Handle by event type (currently only submission.created) if (event.type === "submission.created") { const { formId, submissionId, fields, submittedAt, isSpam } = event.data; // Skip spam if (isSpam) { return new Response("OK"); } console.log(`New submission for form ${formId}:`, fields); // Your logic here: save to DB, send notification, etc. } return new Response("OK"); } type FormtorchEvent = { type: "submission.created"; data: { formId: string; submissionId: string; fields: Record<string, string>; submittedAt: string; isSpam: boolean; spamScore: number; }; };

Webhook payload shape

{ "type": "submission.created", "data": { "formId": "a1b2c3d4e5", "submissionId": "x9y8z7w6v5", "fields": { "name": "Alex", "email": "alex@example.com", "message": "Hello" }, "submittedAt": "2025-03-15T14:30:00.000Z", "isSpam": false, "spamScore": 0 } }

Reserved _* fields (like _redirect, _honeypot) are stripped from fields before delivery.

Always verify the signature

Skipping signature verification means anyone can send fake events to your endpoint.

Always verify x-formtorch-signature before processing. Read the raw request body as a string before parsing it. If you parse JSON first, the string representation may differ and the signature check will fail.

Retry behavior

If your endpoint returns a non-2xx response or times out, Formtorch retries with exponential backoff: 30 seconds, 5 minutes, 30 minutes. After 3 retries, the delivery is marked failed.

Return 200 OK quickly, then process asynchronously if your logic takes time.

Testing locally

Use a tunnel tool like ngrok  or Cloudflare Tunnel  to expose your local server to the internet during development. Then add the tunnel URL as a webhook endpoint in the dashboard.

Learn more

Last updated on