Send submissions to a webhook

A webhook is a single HTTP POST from formformform to an address you control, sent as soon as a form is submitted. An independent bike workshop taking service bookings can have each one land on its own job board without anyone opening the Submissions tab first.

One event exists: form.submitted. Delivery is queued rather than inline, so the person filling in the form never waits on your server, and a slow endpoint of yours cannot make the form look broken to them.

Add the endpoint

Have the receiving URL answering before you add it. That can be a Zapier catch hook, a Make or n8n webhook node, or a route on your own server — the field's placeholder shows a Zapier address, but any reachable https URL is accepted, up to 2048 characters.

  1. 1

    Open the form and switch to the Settings tab.

  2. 2

    Click the Integrations sub-tab. Zapier sits at the top of it, Webhooks underneath.

  3. 3

    Click Add Webhook.

  4. 4

    Paste your address into Webhook URL.

  5. 5

    Fill in Secret (optional) if you want the request signed — see below.

  6. 6

    Click Add Webhook again. A "Webhook added" toast confirms it and the URL joins the list above.

The webhook saves on its own — it does not wait for the Save button on the rest of the settings form. A form can hold several webhooks, and every active one gets its own delivery. Once saved, a row can only be switched on or off, or deleted; to change a URL or a secret, delete the row and add it again.

What lands on your endpoint

The body is JSON with four top-level keys: event, timestamp, form and submission. Under form you get uuid, title and slug. Under submission you get uuid, created_at and fields.

fields is keyed by each field's own label, so renaming a question from "Bike model" to "Make and model" changes the key your code reads. Checkbox answers arrive as an array of the ticked options; everything else arrives as a string. A field nobody answered is left out of the object entirely, so read defensively.

Two headers ride along: X-Webhook-Event, always form.submitted, and X-Webhook-Id, the webhook's own identifier — useful when one endpoint serves several forms.

An encrypted form has nothing readable to send. Its submission object carries "encrypted": true and the armoured ciphertext in encrypted_payload instead of fields, so a webhook there can log that a response arrived but cannot read it. A File Upload field's value is the upload's internal identifier, not a filename or a link, and the download route is owner-only — your endpoint cannot fetch the file, so collect attachments from the submission page.

Verify the request with a secret

Anyone who learns your URL can post to it. Filling in Secret (optional) — up to 255 characters — makes every delivery carry an X-Signature header: an HMAC-SHA256 of the exact JSON body, hex encoded, keyed on that secret. Recompute it over the raw request body before you parse anything, because re-serialising the JSON changes the bytes and the signature will not match.

With the secret left empty, no X-Signature header is sent at all. Treat a missing header as unverified rather than as valid, or the check is worth nothing.

Watch deliveries and retries

Each row in the list shows what happened last: "Never triggered" until the first submission, then a date and a badge holding the last HTTP status code — quiet for a 2xx, red for anything else.

A 2xx is the only response counted as success, and you have 15 seconds to give one. Anything else is retried, three attempts in all, spaced roughly a minute and then five minutes apart. A status of 0 means no answer came back at all: an unreachable host, a TLS failure, or a request that ran past the timeout.

The switch beside each row pauses a webhook without losing it, which is what you want during maintenance on your side. The bin icon removes it for good and asks first.

Because failed deliveries are retried, the same submission can reach you twice — once from a request that timed out after your code had already done the work, once from the retry. Deduplicate on submission.uuid, which stays the same across every attempt.

Frequently asked questions

How do I test a webhook without waiting for a real submission?

There is no test button. Submit the form yourself through Preview or its public link, then look at the row's status badge a moment later. Delete the test response from the Submissions tab afterwards so it does not pollute your export.

Why does my webhook show a status code of 0?

Zero is recorded when no HTTP response came back: the host could not be resolved or refused the connection, TLS failed, or the request ran past the 15-second timeout. It is not a code your server sent. Check the URL is publicly reachable — a localhost or private-network address will never work.

Can a webhook send data back into formformform?

No. Webhooks only leave — a submission arrives, a POST goes out. Nothing you send to that URL comes back the other way, and there is no two-way sync with the connected app. Reading or changing forms programmatically is the REST API's job instead.

Related articles

Nothing here answering your question? The editor is quick enough that trying it is often faster than reading about it.

Open the editor