Webhooks

7 min read15
Copy for AI:

A webhook delivers each response to a form, the moment it arrives, to an address of yours: a Zapier, Make or n8n flow, or your company's own system. At Clínica Vitta, an aesthetics clinic in Miami, every intake form submitted lands straight in the scheduling system, with no one copying data by hand.

Where it is

Forms › (form) › Integrations tab › "Webhooks" card. The tab only shows for the account owner: a webhook sends responses outside, and that is a decision for whoever is responsible for the account. Each form takes up to 3 webhooks; once all three exist, the New webhook button is unavailable.

Create a webhook

  1. Click New webhook.
  2. In Address that receives the responses, paste the URL of your flow or system. It must start with https:// and be a public internet address. Internal network addresses, or URLs with a username and password built in, are refused.
  3. Click Create webhook.

It starts On: the form's next response is already sent.

The secret shows only once

Right after you create it, the card shows the Secret of the webhook (it starts with whsec_). Click Copy, store it in your system and only then click I have copied it. It is not shown again: the list only shows the last characters ("ends in 4f2a").

Lost the secret, or someone who knew it left the team? Use Change secret in the row menu. The current secret stops working right away and the new one shows once, the same way. Until you update it in the receiving system, deliveries arrive with a signature it does not recognize.

What reaches the destination

For each response, a POST with a JSON body and four headers:

  • X-WiseData-Signature — the signature, as sha256=….
  • X-WiseData-Timestamp — when it was sent, in seconds.
  • X-WiseData-Delivery — the delivery id.
  • X-WiseData-Event — submission.created (response) or ping (test).

A Clínica Vitta intake form arrives like this:

{
  "id": "9b2f6c1e-4d7a-4f0b-8a3e-2c5d1e7f9a10",
  "event": "submission.created",
  "created_at": "2026-10-10T14:32:05-03:00",
  "form": { "id": "Xk29fQa7Lm", "title": "Intake form" },
  "submission": {
    "number": 37,
    "submitted_at": "2026-10-10T14:32:04-03:00",
    "answers": [
      { "field_id": "f_a1b2c3d4e5f6", "label": "Full name", "type": "short_text", "value": "Mary Smith" },
      { "field_id": "f_b2c3d4e5f6g7", "label": "Email", "type": "email", "value": "mary@example.com" },
      { "field_id": "f_c3d4e5f6g7h8", "label": "ID document", "type": "document", "value": "X0000000" },
      { "field_id": "f_d4e5f6g7h8i9", "label": "Have you had any aesthetic procedure?", "type": "yes_no", "value": "Yes" },
      { "field_id": "f_e5f6g7h8i9j0", "label": "Package price", "type": "currency", "value": "$1,250.50", "currency": "USD", "amount": "1250.50" },
      { "field_id": "f_f6g7h8i9j0k1", "label": "Signature", "type": "signature", "value": null, "signed": true },
      { "field_id": "f_g7h8i9j0k1l2", "label": "Source", "type": "hidden", "value": "instagram" }
    ]
  }
}
  • id — the delivery id, the same as the X-WiseData-Delivery header. It stays the same when a delivery is sent again: use it so you never process the same response twice.
  • number — the form's "Response #", the same one shown in the dashboard, the email and the PDF.
  • answers — only what the person actually answered. A question hidden by logic is not included; text blocks and sections are not either. Hidden fields (source, campaign) are.
  • value — the text as shown in the dashboard, in the form's language (the option label, "Yes"/"No"). An unanswered question comes as null.
  • Money — besides the formatted value, you get currency (the currency code) and amount (the number with a decimal point). To add up or store in a database, use amount.
  • Signature — only "signed": true, never the image.
  • field_id — does not change when you rename the question. Map by field_id, not by label.

Check the signature in your system

This is what guarantees the call came from Forms and not from someone who found your address. Whoever builds the receiving system does this:

  1. Read the body exactly as it arrived, before parsing the JSON. Reformatting changes the bytes and the check fails.
  2. Build the text: the X-WiseData-Timestamp value + . + the X-WiseData-Delivery value + . + the body.
  3. Compute the HMAC-SHA256 of that text with the secret, in hexadecimal, and compare it with what follows sha256= in X-WiseData-Signature.
  4. Reject calls with an old timestamp (a few minutes is enough) and delivery ids you have already processed.

Answer with a 2xx code within 10 seconds. If processing is heavy, store the response, return 200 and process it afterwards.

Test before the first response

In the row menu, Send test calls the destination right away with the ping event, a null number and sample answers: each question of the form with the value "Sample" (or the first option, for choice questions), money as 1250.50 and the signature as signed. Use it to build the mapping in Zapier, Make or n8n before a real response arrives. The result shows right away: "The destination answered 200: the test arrived." A failed test is not retried and does not count toward automatic shut-off.

Delivery history and sending again

View deliveries opens the last 30 days of deliveries: When, What was sent (Test, "Response #37" or "Deleted response"), Result (Delivered, Retrying or Failed, with the reason, the HTTP code and the time) and Attempts. The content sent is not stored; the history shows what happened to each delivery.

A failed response delivery gets a Send again button, as long as the response exists. It keeps the same delivery id, so your system recognizes the repeat. Tests are not sent again: send a new one.

When the destination fails

  • No connection, timeout, or a 408, 429 or 5xx answer — Forms retries on its own: right away, then after 1 minute, 5 minutes, 30 minutes and 2 hours (5 attempts). Meanwhile the delivery shows as Retrying.
  • Any other code, including redirects (3xx) — fails for good, since retrying does not fix a setup error. Forms does not follow redirects: register the final address.
  • 20 failures in a row — the webhook turns itself off, shows as Off due to failures and the owner gets an email. Fix the destination and use Turn on: the count starts over. Responses that arrive while the webhook is off do not create deliveries.

The same menu has Change address (changes only the URL, without turning it on or off), Turn off/Turn on and Delete webhook, which also deletes the delivery history and cannot be undone.

Creating, changing, deleting, changing the secret, testing and sending again are recorded in Logs, without the address and without the secret.

Where to go next

  • Google Sheets (Integrations collection) — each response as a spreadsheet row, no coding.
  • ID document, address, phone and amounts (Fields collection) — how the money field stores currency and amount.
  • Exporting responses to CSV or XLSX (Responses collection) — to take the whole history at once.
  • Logs (Security and privacy collection) — who changed the webhooks and when.

In short: in the form's Integrations tab, the owner creates up to 3 webhooks with a public https address; the secret shows once and lets your system check the signature of each POST; the JSON carries the response number and the visible answers, with currency and amount on the money field; Forms retries temporary failures on its own, turns the webhook off after 20 failures in a row and lets you send again from the 30-day history.

Was this article helpful?