Typeform Webhook: Example Payload, Headers and Signature

A Typeform webhook sends one form submission: the questions as they were defined and the answers as they were given, in two separate arrays you have to join yourself. Here is a response and the base64 signature that accompanies it.

No webhook URL yet. Creating one needs no account.

Request details
Requests sent to your webhook URL appear here: method, headers and the body exactly as it arrived.

The button posts the example below to your webhook URL from your browser. It is not sent by Typeform, it carries your browser's User-Agent, and its signature is a placeholder that will not verify. To see a real delivery, follow the steps further down.

Last updated . How this page is checked.

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

HeaderExampleWhat it is
Typeform-Signaturesha256=1k9Ld0vVPxGK1c5vCmM1Qd0p3n1vC0mE9yJQ…HMAC-SHA256 of the body, base64, prefixed sha256=. Present only when the webhook has a secret.
Content-Typeapplication/jsonThe body is JSON.
User-AgentTypeform WebhooksIdentifies 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

  1. Press Create webhook URL at the top of this page and copy the address.
  2. Open a form in Typeform and go to Connect → Webhooks.
  3. Add a webhook, paste the address, and save. Edit it to add a secret if you want to see the signature header.
  4. Switch the webhook on, then use the option to send a test request or submit the form yourself.
  5. 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

Other webhooks signed with an HMAC of the body

References

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.