Form webhooks: send submissions to your endpoint
Every submission can be POSTed to a URL of yours as JSON, with an optional secret so your endpoint can tell a real submission from anyone who has guessed the URL.
What is a webhook?
A webhook is an HTTP request your server receives when something happens somewhere else — here, a POST with a JSON body, sent the moment a form is submitted. The difference from an API is the direction of the call: with an API your code asks for data when it wants it, and with a webhook the data arrives on its own, so nothing has to poll and nothing waits. The endpoint is yours; all this side needs is a URL that answers 2xx.
Every submission is delivered once, with a 5-second timeout, and delivery is attempted in parallel with every other destination on the form. A non-2xx reply counts as a failed delivery — the response is still stored, and still exportable, so a broken endpoint never costs you the answer.
Set one up
- Open your form, then the gear icon in the header.
- Go to Storage & Results and choose 🔗 Webhook.
- Paste the URL that should receive submissions.
- Optionally set an authentication header — see below — then Save Settings.
Responses are always stored in Seagit as well. A webhook is an additional destination, never a replacement, so a broken endpoint cannot cost you an answer.
How do I test a webhook?
You do not need to build anything to see a real delivery. webhook.site gives you a throwaway URL that displays whatever is sent to it, headers included.
- Visit webhook.site. It generates a unique URL as soon as the page loads — something like
https://webhook.site/0689a872-384b-4a37-95b8-4ee404282690. - Copy it into the webhook URL field in your form settings and save.
- Open your form's public link and submit an answer. You can use Preview from the editor.
- The request appears on the webhook.site tab straight away. Check the body against the sample below, and the Headers tab to confirm your secret arrived.
Because webhook.site shows the headers it received, it is also the quickest way to confirm your authentication header is named and spelled the way your real endpoint expects.
What does the webhook payload look like?
A single POST with Content-Type: application/json:
{
"responseId": "response-1787712439855-b0t71",
"formId": "g-1787690899982-nd90p",
"submittedAt": 1787712439855,
"data": {
"shortText": "1112222",
"component-1787692637208": 1
},
"fields": [
{ "id": "shortText", "label": "Short Text", "type": "text" },
{ "id": "component-1787692637208", "label": "Number", "type": "number" }
],
"respondent": {},
"metadata": {
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) …",
"referrer": "",
"submittedFrom": "https://forms.seagit.com/forms/view/g-1787690899982-nd90p"
}
}| Field | What it is |
|---|---|
responseId | Unique ID for this submission. Useful as an idempotency key. |
formId | The form the answer came from. |
submittedAt | Milliseconds since the Unix epoch, not an ISO string. |
data | The answers, keyed by field ID. Only answered fields appear — a question left blank is absent rather than null. |
fields | One entry per key in data, giving its label and type, so you do not have to fetch the form to know what a key means. |
respondent | email and name when the form collects them. An empty object when it does not — as in the sample above. |
metadata | Browser userAgent, referrer, and the page the form was submitted from. |
Give your fields readable IDs
In the sample, shortText was renamed but component-1787692637208 was not. The second is the ID generated when a field is created — it works, but it tells your endpoint nothing.
Hover a field in the editor and click its gear to set the ID to something like firstName. Letters, numbers, hyphens and underscores; no spaces. It is the key the answer is stored and sent under, in Seagit and in the payload.
Renaming an ID is a schema change. Answers already collected stay under the old key and will not merge with new ones, so rename before you start collecting rather than after.
How do I authenticate the webhook?
A webhook URL is a secret only until it leaks. Set a token and your endpoint can reject anything that does not carry it.
- Header name — leave blank to use
Authorization, or set your own, e.g.X-Webhook-Secret. - Token or password — sent exactly as you type it. Nothing is added, so if your endpoint expects
Bearer abc123, typeBearer abc123.
Compare the value in constant time where you can, and always serve your endpoint over HTTPS — the header travels with every request.
What happens when a delivery fails?
Worth knowing before you depend on it:
- Each destination is given 5 seconds. A slower endpoint is abandoned.
- There are no retries. A failed delivery is not sent again, so treat the webhook as a notification and Seagit as the record. Every response remains available in your dashboard and in the CSV export.
- A failing webhook never fails the submission. The respondent sees success and their answer is stored either way.
- Destinations are delivered independently — one being down does not affect the others.
- Reply with any 2xx. The response body is ignored, so returning quickly is better than returning something detailed.
Why is my webhook not receiving anything?
| Check | Why |
|---|---|
| Does webhook.site receive it? | Separates "Seagit is not sending" from "my endpoint is rejecting". Do this first. |
| Is the URL publicly reachable? | localhost and private addresses cannot be reached from our servers. Use a tunnel for local development. |
| Does it answer within 5 seconds? | Acknowledge first, do the slow work afterwards. |
| Did the settings save? | Reopen the gear and confirm the URL is still there. |
Common questions
- How do I test a webhook without writing any code?
- Open webhook.site, copy the URL it generates, paste it into your form's webhook settings, and submit an answer. The request appears there immediately with its headers, so you can check both the payload and your authentication header.
- How do I authenticate the request?
- Set a token, and optionally the header to send it in. Leave the header name blank and it is sent as Authorization. The token is sent exactly as typed, so include Bearer yourself if your endpoint expects it.
- Will a failed delivery be retried?
- No. Each destination gets five seconds and one attempt. The response is still stored in Seagit and available in your dashboard and CSV export, so nothing is lost — but it will not be re-sent.
- Why are my payload keys called component-1787692637208?
- That is the ID generated when the field was created. Hover the field in the editor, click the gear, and rename it to something like firstName. Do it before collecting answers, because existing responses stay under the old key.
- Can my webhook fail a submission?
- No. The respondent sees success and the answer is stored regardless of what your endpoint does.