Developer endpoints
LiveSend 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.
What lands in Webhooks
- A JSON POST with the submission, form metadata and spam verdict
- An HMAC-SHA256 signature header computed over the raw body
- A timestamp header so you can reject replayed requests
- Automatic retries with exponential backoff across several attempts
- A delivery log with request, response and one-click replay
Setup
Add a webhook destination
Point it at an HTTPS URL you control and copy the signing secret the dashboard generates.
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.
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.
{
"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.
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
- Always verify against the raw request body. Parsing and re-serialising the JSON changes the bytes and the signature will never match.
- Compare signatures with a constant-time function. A plain === leaks timing information.
- Your endpoint should be idempotent: a retry after a timeout can deliver the same submission twice, and the submission id is stable across attempts.
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.
The delivery is retried with exponential backoff across several attempts. The submission is stored regardless, and you can replay any delivery from the log once your endpoint recovers.
Yes. Add multiple webhook destinations and they all fire, each with its own retry state and delivery log.
The payload includes file metadata and a time-limited download URL rather than the bytes, so your endpoint decides whether to fetch them.
Other destinations
Send your next form submission to Webhooks
Free for 300 submissions a month. Endpoint in under a minute, no card required.
Start free