Blog

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.

Preet SutharFounder, Ikiform

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.

How a submission gets from your form to Zapieryour formany html form,framework orserverIkiformstores it,signs awebhookZapiercatch rawhook, codestep, filterany appCRM, helpdesk, email,sheetpostsignedactionsHow a submission gets from your form to Zapieryour formany html form, framework orserverIkiformstores it, signs a webhookZapiercatch raw hook, code step, filterany appCRM, help desk, email, sheetpostsignedactions
  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.
The Zap, step by stepCatch RawHookanswers 200right awayCodevalid, type,field_*Filtervalid andsubmission.createdyour actionsmap field_emailand the reststops hereno action runsnot valid, or a testThe Zap, step by stepCatch Raw Hookanswers 200 rightawayCodevalid, type, field_*Filtervalid andsubmission.createdyour actionsmap field_email andthe reststops hereno actionrunsnot valid, or a test
  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
{
  "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 eventWhat you getUse it when
Catch Raw HookThe body as one unparsed string, plus the request headersYou want to verify the signature. This guide uses it
Catch HookThe JSON parsed into fields such as data fields emailYou 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

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

  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.
  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
    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
    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)])),
    };
    How the Code step checks an Ikiform signaturewebhook-idwebhook-timestampwithin 10 minraw bodyexact bytessecretbase64 after whsec_HMAC-SHA256of id.timestamp.bodywebhook-signaturev1,… one per secretany v1 match?valid: truefields split intofield_*valid: falsethe filterstops the zapkeyexpectedyesnofrom the requestyours, never sentHow the Code step checks an Ikiform signaturewebhook-idwebhook-timestampwithin 10 minraw bodyexact bytessecretbase64 after whsec_HMAC-SHA256ofid.timestamp.bodywebhook-signaturev1,… one per secretany v1match?valid: truefields split intofield_*valid: falsethe filter stops thezap
    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.
  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.

  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
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 answersIkiform
Any 2xx within 15 secondsMarks the delivery succeeded
A redirectDoesn't follow it, and counts a failure
429 or 503 with Retry-AfterWaits that long, up to the next step of the schedule
410 GoneFails the delivery and disables the endpoint
Anything else, or no answer in 15 secondsRetries

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

ProblemWhat to do
valid is false for every requestCheck 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 sometimesThe 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 logThe Zap is off or was deleted. Turn it on, then Retry now the failed deliveries in Ikiform.
The Code step fails with a time limitCode 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 disabledEvery 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 allCheck 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 toRead
Run the same flow without per-task pricingSend form submissions to n8n
Build it in Make insteadSend form submissions to Make
See every header and retry ruleWebhooks

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.