Zapier7 min read
Send form submissions to Zapier
Start a Zap from every Ikiform submission with Webhooks by Zapier, check the signature in a Code step, and send the fields to any of Zapier's apps.
Zapier connects to thousands of apps, so it's a quick way to get Ikiform submissions into a CRM, a help desk or an email tool without writing a server. This guide catches the raw delivery, verifies it in a Code step, and only lets real submissions through.
- A visitor submits your form, and it posts to Ikiform.
- Ikiform stores the submission and posts a signed webhook to a Zapier catch hook.
- A Code step checks the signature, and a Filter step stops anything that isn't a verified submission.
- Your actions send the fields to a CRM, a help desk, email or a sheet.
What you'll build
- A Zap triggered by Webhooks by Zapier with the Catch Raw Hook event.
- A Code by Zapier step that checks Ikiform's signature and splits the fields into plain values.
- A Filter step, so only verified submissions reach your actions.
- Catch Raw Hook receives the raw body and headers, and Zapier answers Ikiform 200 right away.
- The Code step checks the signature and outputs valid, type and one field_ value per form field.
- The Filter step continues only when valid is true and type is submission.created. Anything else stops there.
- Your actions map field_email, field_name and the rest.
Before you start
- An Ikiform form that already receives submissions.
- A Zapier plan that includes webhooks. Webhooks by Zapier isn't available on the Free plan.
What Ikiform sends
Every delivery is a POST with a JSON body. The body has the same shape for every endpoint, and you can't change it. A submission looks like this:
{
"id": "dlv_01k7v2d4n8r2p6s0w3y5b7c9e1",
"type": "submission.created",
"timestamp": "2026-10-23T12:00:00.000Z",
"api_version": "2026-10-17",
"data": {
"submission_id": "sub_01k7v2c9t3e8m5q4x6z0a1b2c3",
"form": { "id": "frm_xxxxxxxxxxxxxxxx", "name": "Contact" },
"fields": { "name": "Ada", "email": "ada@example.com", "message": "Hello" },
"meta": {
"user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"origin": "https://example.com",
"referrer": "https://example.com/contact",
"country": "GB",
"subject": null,
"reply_to": "ada@example.com"
},
"created_at": "2026-10-23T12:00:00.000Z",
"truncated": false
}
}The form's values are in data.fields, keyed by the input name. A test from the dashboard sends an endpoint.test event instead, with data.endpoint_id and data.message and no fields. The full reference is on Webhooks.
Catch Raw Hook or Catch Hook
| Trigger event | What you get | Use it when |
|---|---|---|
| Catch Raw Hook | The body as one unparsed string, plus the request headers | You want to verify the signature. This guide uses it |
| Catch Hook | The JSON parsed into fields such as data fields email | You only need quick mapping and accept that anyone with the URL can start the Zap |
Catch Hook is simpler, but the Zap can't tell an Ikiform delivery from a request someone else made up. If you use it, keep the URL private and treat the data as unverified.
Set it up
Add the trigger
- Create a Zap. For the trigger, pick Webhooks by Zapier and the event Catch Raw Hook.
- Choose Continue until the Test tab shows the webhook URL. It looks like
https://hooks.zapier.com/hooks/catch/123456/abcdef/. - Copy the URL.
Add the endpoint in Ikiform
- In the Ikiform dashboard, open Webhooks and choose Add endpoint.
- Paste the Zapier webhook URL into URL.
- Under Forms, pick one form, or All forms in the workspace.
- Add a Description if you like, such as "Zapier", then choose Add endpoint.
- Copy the signing secret. It starts with
whsec_and is shown in full only once.
There's no event picker. An endpoint gets
submission.createdfor every accepted submission on the forms you chose, andendpoint.testwhen you send a test. A workspace on Pro can have up to 3 endpoints.Catch a real submission
Send one submission from your form. Back in Zapier, choose Test trigger and pick the request that arrived. You should see a raw body and the request headers, including the three
webhook-*headers.Zapier's docs don't list the exact names of the raw body and header fields, so pick them from your own test data in the next step rather than typing names by hand.Verify the signature in a Code step
Add an action: Code by Zapier, event Run Javascript. Under Input Data, add these five entries and map each one from the trigger:
Input Data secret whsec_paste_your_signing_secret_here id the webhook-id header from the trigger timestamp the webhook-timestamp header from the trigger signature the webhook-signature header from the trigger body the raw body from the triggerPaste this into Code:
Run Javascript const crypto = require("crypto"); const MAX_AGE_SECONDS = 600; const { secret, id, timestamp, signature, body } = inputData; const key = Buffer.from(String(secret).replace(/^whsec_/, ""), "base64"); const expected = crypto.createHmac("sha256", key).update(`${id}.${timestamp}.${body}`).digest(); const age = Math.abs(Date.now() / 1000 - Number(timestamp)); const matches = String(signature || "") .split(" ") .some((entry) => { const [version, value] = entry.split(","); const received = Buffer.from(value || "", "base64"); return version === "v1" && received.length === expected.length && crypto.timingSafeEqual(received, expected); }); const valid = Boolean(id && timestamp && age <= MAX_AGE_SECONDS && matches); const event = valid ? JSON.parse(body) : null; const fields = event && event.type === "submission.created" ? event.data.fields : {}; const text = (value) => { if (value === null || value === undefined) return ""; if (Array.isArray(value)) return value.map(text).join(", "); if (typeof value === "object") return JSON.stringify(value); return String(value); }; output = { valid, type: event ? event.type : "", delivery_id: event ? event.id : "", form: event && event.data.form ? event.data.form.name : "", submission_id: event && event.data.submission_id ? event.data.submission_id : "", ...Object.fromEntries(Object.entries(fields).map(([name, value]) => [`field_${name}`, text(value)])), };- Read webhook-id, webhook-timestamp and the raw body exactly as received.
- Reject the request if the timestamp is more than 10 min from now.
- Base64-decode the part of the secret after whsec_ and use it as the HMAC key.
- Compute HMAC-SHA256 of id.timestamp.body and base64-encode it.
- Compare it, in constant time, with each v1 value in webhook-signature. There are two while a secret rotates.
- If one matches: valid: true, fields split into field_*.
- If none match: valid: false, the filter stops the zap.
- The HMAC uses the raw body exactly as Ikiform sent it, so the comparison holds byte for byte.
validistrueonly when av1signature matches and the timestamp is recent. The check allows 10 minutes, a little more than usual, because Zapier can hold a request for a moment before it runs the Zap.- Each form field becomes its own output, such as
field_emailandfield_message, ready to map in later steps.
Zapier passes every Input Data value as a string, and anyone who can edit the Zap can read the secret. Keep the Zap in a folder only your team can open.Only continue if valid
- Add a Filter step after the Code step.
- Condition one:
valid(Boolean) is true. - Condition two, with And:
type(Text) exactly matchessubmission.created.
The second condition stops dashboard test events, which carry no fields, from reaching your actions.
Add your actions and publish
Add the actions you want, such as creating a contact or posting a message, and map
field_email,field_nameand the rest from the Code step. Then publish the Zap. A Zap that is off answers404, which Ikiform retries.
Ikiform only delivers to public https:// URLs on port 443. IP addresses, localhost, internal hostnames, workers.dev and pages.dev are refused when you save the endpoint.
Test it
Open the endpoint in Ikiform and choose Send test. Ikiform sends one endpoint.test event with no retries, and the result shows up in the endpoint's delivery log within a few seconds. Then send a real submission from your form, or from a terminal:
curl https://ikiform.com/f/frm_xxxxxxxxxxxxxxxx \
-H "Content-Type: application/json" \
-d '{"name":"Ada","email":"ada@example.com","message":"Hello from curl"}'A 201 with "ok": true means Ikiform stored it. The delivery appears on the endpoint page, and the message lands in Zapier's Zap history.
The test event stops at the Filter step. A real submission passes the Filter and runs your actions.
How delivery works
| The receiver answers | Ikiform |
|---|---|
Any 2xx within 15 seconds | Marks the delivery succeeded |
| A redirect | Doesn't follow it, and counts a failure |
429 or 503 with Retry-After | Waits that long, up to the next step of the schedule |
410 Gone | Fails the delivery and disables the endpoint |
| Anything else, or no answer in 15 seconds | Retries |
Failed deliveries are retried 8 times over about 3 d 6 h, with the same webhook-id each time. If every delivery to an endpoint fails for 5 days, Ikiform disables it and emails the workspace owners.
Zapier always answers 200
Zapier answers every caught request with 200 before the Zap runs, and the answer can't be changed. Ikiform marks the delivery succeeded as soon as Zapier has it. If a later step fails, Ikiform doesn't retry; replay the run from Zap history instead.
Troubleshooting
| Problem | What to do |
|---|---|
valid is false for every request | Check the five Input Data mappings. body must be the raw body, not a parsed field, and secret must be this endpoint's whsec_ secret. |
valid is false only sometimes | The run started more than 10 minutes after Ikiform signed it. Raise MAX_AGE_SECONDS, or replay the run from Zap history. |
404 in the delivery log | The Zap is off or was deleted. Turn it on, then Retry now the failed deliveries in Ikiform. |
| The Code step fails with a time limit | Code steps have a short runtime on lower plans. This step only hashes one string and should finish well inside it; check you didn't add network calls. |
| Endpoint is disabled | Every delivery failed for 5 days, the URL answered 410, or its hostname started resolving to a private address. Fix the cause, then turn Enabled back on and Retry now the failed deliveries. |
| No delivery at all | Check that the endpoint covers this form, that it's enabled, and that the submission wasn't marked as spam or held because the workspace is over its monthly limit. |
Avoid duplicate records
A delivery can reach Zapier twice if an earlier attempt timed out on the way. The Code step outputs delivery_id, which stays the same across retries. Use it as a unique key in the app you write to, or look it up before you create a record.
Next steps
| Want to | Read |
|---|---|
| Run the same flow without per-task pricing | Send form submissions to n8n |
| Build it in Make instead | Send form submissions to Make |
| See every header and retry rule | Webhooks |
Zapier's own reference: Trigger Zaps from webhooks, JavaScript in Zaps and Code by Zapier rate limits.
Stuck on a step, or want a guide for another tool? Email hello@ikiform.com.