Skip to content

Webhooks, Zapier and Make

A webhook sends each response to a URL as a POST request with JSON, within seconds of submission. “Failed deliveries are retried for a day.”

  1. Open the form, then Settings → Integrations → Webhooks, and select Add webhook.
  2. URL to send to: a public https:// address. “For Zapier or Make, paste the URL their webhook step gives you.”
  3. Description (optional), such as CRM sync.
  4. Send when: the events to send (see below).
  5. Save. Copy this signing secret now. It’s shown only once. Select I’ve saved it.
  6. Select Send test to send a sample delivery and check that your endpoint receives it.

Each webhook has Edit, New secret (rotate the signing secret) and Delete.

EventSent when
submission.createdA response is accepted. On forms that take payment after submitting, it’s sent once the payment settles.
payment.succeededA payment is confirmed.
payment.failedA payment is declined or cancelled.

Headers:

HeaderMeaning
X-KyraForms-EventThe event name
X-KyraForms-DeliveryUnique ID of this delivery. A delivery can occasionally arrive twice, so use it to ignore repeats.
X-KyraForms-Signaturet=<unix seconds>,v1=<hex HMAC-SHA256>

Until recently these headers were called X-EasyForm-Event, X-EasyForm-Delivery and X-EasyForm-Signature. Deliveries still include the old names, with the same values, so nothing you’ve already built breaks. The old names are deprecated, so switch to the X-KyraForms-* ones when you can.

Body:

{
"id": "cmvh…",
"event": "submission.created",
"api_version": "2026-10-01",
"created_at": "2026-10-21T09:30:00.000Z",
"form": { "id": "…", "name": "Apply", "slug": "apply" },
"submission": {
"id": "…",
"created_at": "…",
"answers": [
{ "field_id": "field_…", "key": "name", "label": "Full name", "type": "text", "value": "Asha", "text": "Asha" },
{ "field_id": "field_…", "key": "field_…", "label": "Plan", "type": "radio", "value": "pro", "text": "Pro plan" }
],
"answers_by_key": { "name": "Asha", "field_…": "pro", "utm_source": "linkedin" },
"hidden": { "utm_source": "linkedin" },
"meta": { "ip": "203.0.113.5", "user_agent": "…" }
},
"payment": { "id": "…", "status": "paid", "amount": 499, "currency": "INR", "gateway": "razorpay", "order_id": "…" }
}
  • key is the question’s Link parameter when it has one, otherwise its ID. Set link parameters for readable keys that don’t change when you edit a label.
  • text is the answer as a person reads it (option labels, not values).
  • File answers include a download url and url_expires_at. Download the file before the link expires.
  • meta is left out on forms that mask identifying information.
  • payment.amount is in the main unit of payment.currency (499 rupees, 19.99 dollars, 500 yen), not in paise or cents. gateway is razorpay, stripe, paypal, payu, paytm or billdesk.
  • New fields may be added. Existing ones won’t be removed or renamed without a new api_version.

Check every request really came from KyraForm. v1 is the hex HMAC-SHA256 of "<t>.<raw request body>", keyed with the webhook’s signing secret. Check it against the raw body before parsing JSON, and refuse requests whose t is more than 5 minutes old. After you rotate the secret, requests carry two v1 values for 24 hours, so accept either.

const crypto = require('crypto');
function verify(header, rawBody, secret) {
const t = Number((header.match(/(?:^|,)t=(\d+)/) || [])[1]);
if (!t || Math.abs(Date.now() / 1000 - t) > 300) return false;
const expected = crypto.createHmac('sha256', secret).update(`${t}.${rawBody}`).digest('hex');
return header.split(',').some((part) => part === `v1=${expected}`);
}
  • Any 2xx reply counts as delivered. Reply within 10 seconds, and do slow work afterwards.
  • Anything else, or a timeout, is retried after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h. Then it’s marked Failed.
  • Replying 410 Gone switches the webhook off at once. It’s also switched off after 50 failed deliveries in a row, and the Dashboard’s Needs attention tells you. Switching it back on resets the count.
  • Redirects aren’t followed. Private, loopback and cloud-metadata addresses are refused.

The delivery log under each webhook shows every delivery’s status (Delivered, Retrying, Sending, Failed, Skipped), Attempts, Next try, how long it Took, and the Problem. Select Send again to resend one. Refresh reloads the log.

  1. In Zapier, add Webhooks by Zapier → Catch Hook. In Make, add Webhooks → Custom webhook.
  2. Copy the URL it gives you.
  3. Add it in KyraForm as a webhook, then select Send test so Zapier or Make can see a sample response.
  4. Map the answers into the next steps of your Zap or scenario.

n8n works the same way with its Webhook node.