# 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.

Published 2026-10-23 · Guide, Zapier · https://ikiform.com/blog/form-submissions-to-zapier

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.

Diagram: How a submission gets from your form to Zapier

1. A visitor submits your form, and it posts to Ikiform.
2. Ikiform stores the submission and posts a signed webhook to a Zapier catch hook.
3. A Code step checks the signature, and a Filter step stops anything that isn't a verified submission.
4. Your actions send the fields to a CRM, a help desk, email or a sheet.

Ikiform posts each submission to a Zapier catch hook. A Code step checks the signature before any action runs.

## 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.

Diagram: The Zap, step by step

1. Catch Raw Hook receives the raw body and headers, and Zapier answers Ikiform 200 right away.
2. The Code step checks the signature and outputs valid, type and one field_ value per form field.
3. The Filter step continues only when valid is true and type is submission.created. Anything else stops there.
4. 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:

`submission.created`:

```json
{
  "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](https://ikiform.com/docs/webhooks.md#payloads).

## 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

### Step 1: Add the trigger

1. Create a Zap. For the trigger, pick **Webhooks by Zapier** and the event **Catch Raw Hook**.
2. Choose **Continue** until the **Test** tab shows the webhook URL. It looks like `https://hooks.zapier.com/hooks/catch/123456/abcdef/`.
3. Copy the URL.

### Step 2: 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.created` for every accepted submission on the forms you chose, and `endpoint.test` when you send a test. A workspace on Pro can have up to 3 endpoints.

### Step 3: 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.

### Step 4: 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`:

```text
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 trigger
```

Paste this into **Code**:

`Run Javascript`:

```js
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)])),
};
```

Diagram: How the Code step checks an Ikiform signature

1. Read webhook-id, webhook-timestamp and the raw body exactly as received.
2. Reject the request if the timestamp is more than 10 min from now.
3. Base64-decode the part of the secret after whsec_ and use it as the HMAC key.
4. Compute HMAC-SHA256 of id.timestamp.body and base64-encode it.
5. Compare it, in constant time, with each v1 value in webhook-signature. There are two while a secret rotates.
6. If one matches: valid: true, fields split into field_*.
7. 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.
- `valid` is `true` only when a `v1` signature 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_email` and `field_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.

### Step 5: Only continue if valid

1. Add a **Filter** step after the Code step.
2. Condition one: `valid` (Boolean) is true.
3. Condition two, with **And**: `type` (Text) exactly matches `submission.created`.

The second condition stops dashboard test events, which carry no fields, from reaching your actions.

### Step 6: Add your actions and publish

Add the actions you want, such as creating a contact or posting a message, and map `field_email`, `field_name` and the rest from the Code step. Then publish the Zap. A Zap that is off answers `404`, 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:

`Terminal`:

```bash
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**.

> If the form has a human check or allowed domains turned on, a post from curl is refused or marked as spam, and spam never sends a webhook. Test from your real site instead.

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](https://ikiform.com/blog/form-submissions-to-n8n.md) |
| Build it in Make instead | [Send form submissions to Make](https://ikiform.com/blog/form-submissions-to-make.md) |
| See every header and retry rule | [Webhooks](https://ikiform.com/docs/webhooks.md) |

Zapier's own reference: [Trigger Zaps from webhooks](https://help.zapier.com/hc/en-us/articles/8496288690317-Trigger-Zap-workflows-from-webhooks), [JavaScript in Zaps](https://help.zapier.com/hc/en-us/articles/8496310939021-Use-JavaScript-code-in-Zaps) and [Code by Zapier rate limits](https://help.zapier.com/hc/en-us/articles/29971850476173-Code-by-Zapier-rate-limits).
