Webhooks
Add a destination URL to a form in the dashboard and we'll POST a signed event there every time someone submits or edits a response.
Payload
{
"id": "evt_01...",
"event": "submission.created",
"created_at": "2026-11-06T18:50:00Z",
"data": {
"submission_id": "01...",
"form_id": "01...",
"form_version": 1,
"revision": 1,
"submission": { "email": "...", "message": "..." }
}
}
submission.created fires the first time a response is finalized.
submission.updated fires on every later edit by the respondent. Edits
made from the admin dashboard don't trigger a webhook.
Verifying the signature
Every request carries these headers:
X-Form-Event | the event type |
X-Form-Event-Id | unique per delivery attempt's source event |
X-Form-Timestamp | unix seconds, when it was sent |
X-Form-Signature | sha256=<hex> |
Recompute it yourself and compare: HMAC-SHA256(secret, timestamp + "." + rawBody),
hex-encoded, where secret is the plaintext value shown once when you
created the webhook.
const expected = "sha256=" + hmacSha256Hex(secret, `${timestamp}.${rawBody}`);
if (expected !== receivedSignature) reject();
Retries
If your endpoint doesn't return a 2xx, we retry up to 7 times on a backoff: immediately, then 1 min, 5 min, 15 min, 1 hour, 6 hours, 24 hours later. A 4xx response (other than 408 or 429) is treated as permanent and isn't retried. Redirects are never followed. Delivery is idempotent — a destination that already succeeded for an event won't be sent it again.
See delivery history and manually retry a destination from the form's page in the dashboard.