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-Eventthe event type
X-Form-Event-Idunique per delivery attempt's source event
X-Form-Timestampunix seconds, when it was sent
X-Form-Signaturesha256=<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.