Skip to Content
API ReferenceWebhooks APISignature Verification

Signature Verification

Every webhook request includes an X-FormTorch-Signature header containing an HMAC-SHA256 signature of the raw request body, signed with your endpoint’s signing secret.

Always verify this signature before processing any webhook payload.

How it works

  1. Formtorch serializes the payload to JSON
  2. It signs the raw JSON string with HMAC-SHA256 using your signing secret
  3. The result is sent as sha256=<hex> in the X-FormTorch-Signature header
  4. Your server recomputes the expected signature and compares it to the header

Verification examples

lib/verify-webhook.ts
import { createHmac, timingSafeEqual } from "crypto"; export function verifyWebhookSignature( body: string, signature: string, secret: string ): boolean { const expected = `sha256=${createHmac("sha256", secret) .update(body) .digest("hex")}`; // Buffer lengths must match before timingSafeEqual — mismatched lengths throw const a = Buffer.from(signature); const b = Buffer.from(expected); if (a.length !== b.length) return false; return timingSafeEqual(a, b); }

Usage in a Next.js route handler:

app/api/webhooks/formtorch/route.ts
import { verifyWebhookSignature } from "@/lib/verify-webhook"; export async function POST(req: Request) { const body = await req.text(); // read raw body BEFORE JSON.parse const signature = req.headers.get("x-formtorch-signature") ?? ""; if ( !verifyWebhookSignature( body, signature, process.env.FORMTORCH_WEBHOOK_SECRET! ) ) { return new Response("Unauthorized", { status: 401 }); } const event = JSON.parse(body); if (event.event === "form.submitted") { const { id, data } = event.submission; console.log(`New submission ${id}:`, data); // your logic here } return new Response("OK"); }

Read the raw body first

Signature verification requires the raw request body string, not the parsed JSON object. If you parse first, the string representation may differ and the check will fail.

In Express, use express.raw({ type: 'application/json' }) on the webhook route instead of express.json(). In Next.js App Router, call req.text() before JSON.parse().

Finding your signing secret

Go to Settings → Webhooks in the dashboard. The signing secret is shown exactly once when you create an endpoint. If you lose it, delete the endpoint and create a new one — the secret cannot be recovered.

Other request headers

In addition to the signature, each request includes:

HeaderValue
X-FormTorch-EventEvent name, e.g. form.submitted
X-FormTorch-DeliveryUnique delivery ID (matches delivery_id in the body)
User-AgentFormTorch-Webhooks/1.0

You can use X-FormTorch-Event to route events without parsing the body first.

Last updated on