Skip to content

Webhooks

A webhook sends each new form submission to a URL you choose, the moment it arrives. Use one to push leads into a CRM, start a Zapier or Make automation, post to Slack, or hit your own endpoint.

Webhooks are in addition to email — turning one on does not stop submissions being emailed to your recipients or stored in your dashboard.

Open the website editor, then go to Settings → Forms, open a form, and click Settings. The Webhooks section is below Captcha Protection.

Paste the URL your tool gave you (Zapier and Make both show one when you create a “Catch Hook” or “Custom Webhook” trigger) and click Add. That is the whole setup — submissions start arriving immediately.

Webhooks are per form. A webhook added to your contact form receives that form’s submissions only, so a site with a contact form and a quote request can send each one somewhere different.

URLs must start with https://. Plain http:// is refused, because your leads would travel unencrypted.

Each submission is POSTed as JSON in the CloudEvents format:

{
"specversion": "1.0",
"id": "evt_8Kd2xR1qP",
"source": "https://aifeatures.dev/sites/site_abc123",
"type": "ai.landingsite.form.submission.created",
"subject": "sub_9xLm2Qa",
"time": "2026-08-27T21:49:27.734Z",
"datacontenttype": "application/json",
"dataschema": "https://aifeatures.dev/schemas/form.submission.created/v1.json",
"data": {
"site_id": "site_abc123",
"form_id": "hAxNkUSczvhN3y",
"form_name": "Repair Quote Form",
"submission_id": "sub_9xLm2Qa",
"submitted_at": "2026-08-27T21:49:27.734Z",
"email": "lead@example.com",
"fields": {
"name": "Sam Lee",
"email": "lead@example.com",
"message": "Do you repair Bambu printers?"
},
"attachments": []
}
}

The submitted values are in data.fields, keyed by your form’s field names. data.email is the lead’s email pulled out of those fields for convenience — it is whatever the visitor typed, so treat it as a contact detail rather than a verified identity.

The full JSON Schema is published at the dataschema URL, so you can validate against it or generate types from it.

If your form accepts uploads, each file appears in data.attachments with a download_url:

"attachments": [
{
"id": "att_1",
"name": "quote.pdf",
"size": 20481,
"content_type": "application/pdf",
"field": "attachment",
"download_url": "https://aifeatures.dev/v1/fd/…/att_1"
}
]

The link needs no API key — the token in it is the credential, so treat the URL as a secret. It expires 7 days after the submission, so fetch and store the file rather than keeping the link.

Most people do not need this. If you want your endpoint to confirm a request really came from us, each delivery is signed with these headers:

HeaderMeaning
webhook-idUnique id for the event
webhook-timestampWhen this attempt was signed
webhook-signatureThe signature

This follows the Standard Webhooks specification, so any Standard Webhooks library will verify it for you. Your signing secret is in the Webhooks section — click Show secret. You can rotate it there too, which immediately invalidates the old one.

If your endpoint is down or returns an error, we retry with increasing gaps over roughly two days. Anything outside a 2xx response counts as a failure.

After repeated failures we stop sending and mark the webhook Disabled, with the reason shown. Fix your endpoint, then click Re-enable.

Click Deliveries on any webhook to see what was sent, when, and what came back — including the error if it failed. Each row has a retry button that re-sends that exact event.

Delivery is at-least-once: a webhook can arrive more than once, most often when your endpoint received it but its reply did not reach us. Use the webhook-id header to recognise an event you have already handled — it stays the same across retries.

  • Spam. Submissions caught by the spam trap are never delivered. They still appear in your dashboard.
  • Sensitive fields. Passwords, card numbers, and similar values are stripped before delivery. Contact forms should not collect them at all.