send.webforms.toRead the docs →

Developer endpoints

Live

Send form submissions to your own API

Sometimes the destination is your own backend, and everything you need is a reliable, verifiable POST. Webhook delivery sends each submission as JSON to a URL you control, signed with your secret so you can prove it came from us, and retried automatically when your endpoint is having a bad day.

Get an endpointRead the docs

What lands in Webhooks

Setup

  1. Add a webhook destination

    Point it at an HTTPS URL you control and copy the signing secret the dashboard generates.

  2. Verify the signature

    Compute HMAC-SHA256 over the raw request body using your secret and compare it to the signature header with a constant-time comparison.

  3. Return 2xx quickly

    Acknowledge fast and do slow work asynchronously. Non-2xx responses and timeouts are retried with backoff.

Webhooks example

Swap in your own endpoint and these run as-is.

What arrives at your endpoint.

payload.json
{
  "id": "sub_2f9x...",
  "formId": "frm_8a1c...",
  "createdAt": "2026-03-04T10:12:44.019Z",
  "data": {
    "name": "Ada Lovelace",
    "email": "ada@example.com",
    "message": "Do you support file uploads?"
  },
  "files": [],
  "meta": {
    "ip": "203.0.113.42",
    "country": "GB",
    "referrer": "https://example.com/pricing",
    "userAgent": "Mozilla/5.0 ..."
  },
  "spam": { "score": 0.02, "quarantined": false }
}

Verify the signature before trusting the body. Use the raw bytes, not a re-serialised object.

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

export function verify(rawBody: string, headers: Headers): boolean {
  const signature = headers.get("x-webforms-signature") ?? "";
  const timestamp = headers.get("x-webforms-timestamp") ?? "";

  // Reject anything older than five minutes to blunt replay attacks.
  if (Math.abs(Date.now() - Number(timestamp)) > 5 * 60 * 1000) return false;

  const expected = createHmac("sha256", process.env.WEBFORMS_SECRET!)
    .update(`${timestamp}.${rawBody}`)
    .digest("hex");

  const a = Buffer.from(expected);
  const b = Buffer.from(signature);
  return a.length === b.length && timingSafeEqual(a, b);
}

Worth knowing

Webhooks form FAQ

Compute HMAC-SHA256 over `timestamp.rawBody` using your signing secret and compare the hex digest to the x-webforms-signature header using a constant-time comparison.

Send your next form submission to Webhooks

Free for 300 submissions a month. Endpoint in under a minute, no card required.

Start free