How the webhook URL works. It is a public address that records whatever is sent to it and
answers 200. An inbox keeps the 50 most recent requests, accepts bodies up to 256 KB, and is
deleted 24 hours after it was created — or sooner if you press Delete URL or our server
restarts, because requests are held in memory and never written to disk.
Anyone who has the URL can read what it received. Use sandbox and test-mode accounts, do not route production traffic or customer data through it, and delete it when you are done. Privacy details.
What is a Typeform webhook?
A Typeform webhook is an HTTP POST sent to a URL attached to a form each time somebody submits it. The body is a JSON object whose event_type is form_response; a separate event type exists for partial responses where that feature is enabled. Everything about the submission sits under form_response.
Two arrays matter. definition.fields describes the questions — id, title, type and a stable ref — and answers holds one entry per question that was answered, each pointing back at its question through field. Hidden fields and calculated variables, if the form uses them, arrive alongside as hidden and variables.
What a Typeform webhook request looks like
The request line and headers as they reach your server, followed by the body. Ids, tokens and the signature are placeholders.
POST /your/webhook/path HTTP/1.1
Host: your-server.example
Typeform-Signature: sha256=1k9Ld0vVPxGK1c5vCmM1Qd0p3n1vC0mE9yJQmHk0b1E=
Content-Type: application/json
User-Agent: Typeform Webhooks
{
"event_id": "01HEXAMPLE0000000000000001",
"event_type": "form_response",
"form_response": {
"form_id": "lT4Z3j",
"token": "a3a12ec67a1365927098a606107fac15",
"landed_at": "2026-01-01T11:58:40Z",
"submitted_at": "2026-01-01T12:00:00Z",
"hidden": {
"campaign": "winter"
},
"definition": {
"id": "lT4Z3j",
"title": "Demo request",
"fields": [
{
"id": "DlXFaesGBpoF",
"ref": "full_name",
"type": "short_text",
"title": "What is your name?"
},
{
"id": "SMEUb7VJz92Q",
"ref": "work_email",
"type": "email",
"title": "And your work email?"
},
{
"id": "JwWggjAKtOkA",
"ref": "team_size",
"type": "multiple_choice",
"title": "How big is your team?"
}
]
},
"answers": [
{
"type": "text",
"text": "Ada Lovelace",
"field": {
"id": "DlXFaesGBpoF",
"type": "short_text",
"ref": "full_name"
}
},
{
"type": "email",
"email": "ada@example.com",
"field": {
"id": "SMEUb7VJz92Q",
"type": "email",
"ref": "work_email"
}
},
{
"type": "choice",
"choice": {
"label": "11-50"
},
"field": {
"id": "JwWggjAKtOkA",
"type": "multiple_choice",
"ref": "team_size"
}
}
]
}
}
The value of an answer lives under a key named after its type: text, email, number, boolean, date, choice (an object with a label), choices (with a list of labels) or file_url. Optional questions that were skipped are simply absent from answers. form_response.token is the id of the submission and is what the Responses API calls response_id.
Headers Typeform sends
| Header | Example | What it is |
|---|---|---|
Typeform-Signature | sha256=1k9Ld0vVPxGK1c5vCmM1Qd0p3n1vC0mE9yJQ… | HMAC-SHA256 of the body, base64, prefixed sha256=. Present only when the webhook has a secret. |
Content-Type | application/json | The body is JSON. |
User-Agent | Typeform Webhooks | Identifies the sender. |
Header names are case-insensitive. Your framework may show them lower-cased, and HTTP/2 always does.
How Typeform signs a webhook
Add a secret to the webhook and Typeform computes an HMAC-SHA256 of the request body with it, base64-encodes the digest, and sends it as Typeform-Signature: sha256=<base64>. The encoding is the detail to watch: code that produces a hex digest, as GitHub-style verification does, will never match.
There is no timestamp in the signed data, so de-duplicate on event_id or the response token if a replay would matter. Compute it over the raw request body exactly as received — before any JSON parsing — and compare with a constant-time function.
const crypto = require('crypto');
// rawBody: Buffer or string of the body exactly as received
function verifyTypeform(rawBody, header, secret) {
const expected = 'sha256=' +
crypto.createHmac('sha256', secret).update(rawBody).digest('base64');
const a = Buffer.from(header || '');
const b = Buffer.from(expected);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
Response time, retries and automatic disabling
Reply with a 2xx promptly; Typeform's documentation at the time of writing allows up to thirty seconds before it counts the attempt as failed. Certain failures are retried with a growing delay over several hours, including timeouts and 429 and 503 responses.
Other answers are taken as a statement about the endpoint itself. Per the same documentation, a 404 or 410 disables the webhook straight away, and a webhook that fails every delivery for long enough is disabled too. A disabled webhook must be switched back on by hand, and submissions made in the meantime are not replayed, although they remain available through the Responses API.
Catch a real Typeform webhook
- Press Create webhook URL at the top of this page and copy the address.
- Open a form in Typeform and go to Connect → Webhooks.
- Add a webhook, paste the address, and save. Edit it to add a secret if you want to see the signature header.
- Switch the webhook on, then use the option to send a test request or submit the form yourself.
- Read the definition and answers in the inbox above.
To run a caught request against your own code, select it and press Open in API Tester: the method, headers and body are loaded into the request console, where you enter your server's public URL and send it. A body you edit will no longer match its signature — that is the signature doing its job.
Things that catch people out
- Match answers by
field.ref, not by title or position. Titles get reworded and logic jumps change the order; a ref you set stays put. - A test request is not a real submission. Its answers are generated placeholders and its ids do not exist in the Responses API.
- Skipped questions are missing, not null. Look answers up by ref and handle the absent case.
api-tester.com is independent and is not affiliated with Typeform. Providers change header names, limits and retry schedules; treat the official Typeform documentation as the source of truth.
Related examples and tools
Calendly Webhook Example
Calendly webhooks are created through the API rather than a settings page, and they describe everything by URI.
Linear Webhook Example
A Linear webhook reports a change to one entity — an issue, a comment, a project — and, on updates, tells you the previous values of whatever changed.
Clerk Webhook Example
Clerk delivers its webhooks through Svix, so the three svix- headers and their signing scheme are shared with every other service that uses the same infrastructure.
Paddle Webhook Example
Paddle Billing notifications look a lot like Stripe's until you parse the signature header, which uses different separators and joins the timestamp to the body differently.
Razorpay Webhook Example
Razorpay wraps each event's entities inside payload under their own names, and signs the body with a secret you choose when creating the webhook.
Hash Generator
SHA-256 and friends in the browser, with the reason request signing uses HMAC instead.
Curl POST JSON
Send a JSON body like this one from a terminal.
Webhook Tester
The inbox on its own page, plus a guide to signatures, retries and idempotent handlers.
All Webhook Examples
30 providers side by side: content type, signature scheme and retry behaviour.
Other webhooks signed with an HMAC of the body
References
- Official Typeform documentation — The provider's own guide to the requests described on this page.
- Typeform Developers: secure your webhooks — Setting a secret and checking the signature.
- Typeform Developers: example webhook payload — A full payload with every answer type.
- RFC 2104: HMAC — The keyed-hash construction this signature is built on.
- Node.js: crypto.timingSafeEqual() — The constant-time comparison used in the code on this page.
These links leave api-tester.com, which is independent of the companies and projects they point to.
Typeform webhook FAQ
What does a Typeform webhook payload look like?
A JSON object with event_id, event_type and form_response. form_response contains form_id, token, landed_at, submitted_at, a definition with the form fields, and an answers array with one entry per answered question.
How do I verify the Typeform-Signature header?
Compute an HMAC-SHA256 of the raw request body with the webhook secret, base64-encode the digest, prefix it with sha256= and compare it to the header using a constant-time comparison.
How do I match answers to questions in a Typeform webhook?
Each answer has a field object with the id and ref of its question. Look the question up in definition.fields by that id or ref.
Does Typeform retry failed webhooks?
It retries some failures, such as timeouts and 429 or 503 responses, over several hours. According to Typeform's documentation at the time of writing, a 404 or 410 disables the webhook immediately.
Why did my Typeform webhook get disabled?
Typeform turns a webhook off when the endpoint answers 404 or 410, or when deliveries keep failing for an extended period. Fix the endpoint and re-enable the webhook in the form settings.