# Send form submissions to Make

> Start a Make scenario from every Ikiform submission with a custom webhook, and add a small relay when you want each delivery verified.

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

Make's custom webhooks accept any JSON, so Ikiform can post to them directly. This guide sets that up first, then shows how to add signature checks with a short relay, because Make can't read Ikiform's signature on its own without extra work.

Diagram: Two routes from Ikiform to Make

1. Direct route: Ikiform posts each signed submission straight to the Make custom webhook. Make can't check the signature, so anyone who learns the URL can start the scenario.
2. Verified route: Ikiform posts to a relay. The relay checks the signature, then forwards the same body to Make with the x-make-apikey header.
3. Make's webhook has an API key on the verified route, so it refuses anything without that header.

The direct route skips the relay. The verified route puts a relay between Ikiform and Make.

## Before you start

- An Ikiform form that already receives submissions.
- A Make account. Each run uses operations from your plan.
- For the verified route, somewhere to run the relay: a Cloudflare account with a domain on it, or a Next.js app.

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

## Pick a route

| Route | Setup | Who can start the scenario |
| --- | --- | --- |
| **Direct** | Paste the Make URL into Ikiform | Anyone who learns the URL |
| **Verified** | Deploy a 30-line relay | Only requests signed with your Ikiform secret |

Start direct to build the scenario. Move to the verified route before the scenario writes anything that matters, such as CRM records or emails.

## Direct route

### Step 1: Add a custom webhook

1. Create a scenario and add the **Webhooks** app with the **Custom webhook** trigger.
2. Choose **Add**, name the webhook, for example `Ikiform`, and save.
3. Copy the address. It looks like `https://hook.eu1.make.com/abc123...`, with the region of your Make account.

### Step 2: Add the endpoint in Ikiform

- In the Ikiform dashboard, open **Webhooks** and choose **Add endpoint**.
- Paste the Make webhook address into **URL**.
- Under **Forms**, pick one form, or **All forms** in the workspace.
- Add a **Description** if you like, such as "Make", 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: Teach Make the data structure

Make learns the fields from the first request it sees. With the webhook module open and waiting, send one real submission from your form, not a dashboard test, because test events have no fields. If you add fields to the form later, open the module and choose **Redetermine data structure**, then send another submission.

### Step 4: Filter and map

Add your next module, such as Google Sheets or Slack. On the link between the two, add a filter where `type` equals `submission.created`. Then map values from `data` > `fields`, such as `email` and `message`.

Turn the scenario on so it runs as soon as data arrives.

> On the direct route, keep the Make address out of public code and screenshots. Make's own API key option for webhooks expects an `x-make-apikey` header, and Ikiform can't add custom headers, so the verified route below is how you lock it down.

## Verified route

The relay checks Ikiform's signature, then forwards the exact same body to Make with an API key header. The scenario stays the same, and Make refuses anything that doesn't carry the key.

### Step 1: Add an API key to the webhook

In Make, open the webhook's settings and add an **API key**. Make then only accepts requests that send that key in the `x-make-apikey` header. Copy the key.

### Step 2: Write the relay

**Cloudflare Worker** (`src/index.ts`)

```ts
import { verifyWebhook, type WebhookEvent, WebhookVerificationError } from "@ikiform/sdk/webhooks";

interface Env {
  IKIFORM_WEBHOOK_SECRET: string;
  MAKE_WEBHOOK_URL: string;
  MAKE_API_KEY: string;
}

function forward(url: string, apiKey: string, payload: string) {
  return fetch(url, {
    method: "POST",
    headers: { "content-type": "application/json", "x-make-apikey": apiKey },
    body: payload,
  });
}

export default {
  async fetch(request: Request, env: Env) {
    if (request.method !== "POST") return new Response(null, { status: 405 });
    const payload = await request.text();
    let event: WebhookEvent;
    try {
      event = await verifyWebhook({ payload, headers: request.headers, secret: env.IKIFORM_WEBHOOK_SECRET });
    } catch (error) {
      if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
      throw error;
    }
    const response = await forward(env.MAKE_WEBHOOK_URL, env.MAKE_API_KEY, payload);
    if (response.status === 429) {
      const retryAfter = response.headers.get("retry-after") ?? "30";
      return new Response("Make rate limit", { status: 503, headers: { "retry-after": retryAfter } });
    }
    if (!response.ok) return new Response(`Make answered ${response.status}`, { status: 502 });
    return new Response(null, { status: 204 });
  },
};
```

**Next.js** (`app/api/ikiform/make/route.ts`)

```ts
import { verifyWebhook, type WebhookEvent, WebhookVerificationError } from "@ikiform/sdk/webhooks";

function forward(url: string, apiKey: string, payload: string) {
  return fetch(url, {
    method: "POST",
    headers: { "content-type": "application/json", "x-make-apikey": apiKey },
    body: payload,
  });
}

export async function POST(request: Request) {
  const payload = await request.text();
  let event: WebhookEvent;
  try {
    event = await verifyWebhook({ payload, headers: request.headers, secret: process.env.IKIFORM_WEBHOOK_SECRET! });
  } catch (error) {
    if (error instanceof WebhookVerificationError) return new Response(error.reason, { status: 400 });
    throw error;
  }
  const response = await forward(process.env.MAKE_WEBHOOK_URL!, process.env.MAKE_API_KEY!, payload);
  if (response.status === 429) {
    const retryAfter = response.headers.get("retry-after") ?? "30";
    return new Response("Make rate limit", { status: 503, headers: { "retry-after": retryAfter } });
  }
  if (!response.ok) return new Response(`Make answered ${response.status}`, { status: 502 });
  return new Response(null, { status: 204 });
}
```

- The relay forwards `payload`, the raw string it verified, so Make sees exactly what Ikiform sent and the data structure doesn't change.
- A `429` from Make becomes a `503` with Make's `Retry-After`, and any other error becomes a `502`. Ikiform retries both.

### Step 3: Deploy it

### Cloudflare Worker

`Terminal`:

```bash
mkdir ikiform-make && cd ikiform-make
npm init -y
npm install @ikiform/sdk
npm install -D wrangler
```

`wrangler.jsonc`:

```json
{
  "$schema": "node_modules/wrangler/config-schema.json",
  "name": "ikiform-make",
  "main": "src/index.ts",
  "compatibility_date": "2026-10-01",
  "routes": [{ "pattern": "hooks.example.com", "custom_domain": true }]
}
```

> Ikiform refuses `workers.dev` URLs, so the Worker needs a hostname on a domain you have on Cloudflare, like `hooks.example.com`. The `custom_domain` route creates the DNS record and certificate for you on deploy.

`Terminal`:

```bash
npx wrangler secret put IKIFORM_WEBHOOK_SECRET
npx wrangler secret put MAKE_WEBHOOK_URL
npx wrangler secret put MAKE_API_KEY
npx wrangler deploy
```

### Next.js

Add `@ikiform/sdk`, save the route handler, and set `IKIFORM_WEBHOOK_SECRET`, `MAKE_WEBHOOK_URL` and `MAKE_API_KEY` in your host's environment variables. Use the production domain, not a protected preview URL.

### Step 4: Point Ikiform at the relay

Open the endpoint in Ikiform, choose **Edit**, and change **URL** to the relay URL. The signing secret stays the same; put it in `IKIFORM_WEBHOOK_SECRET` on the relay.

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 Make's scenario **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 runs the scenario and stops at the filter. A real submission continues through it.

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

Diagram: What the relay tells Ikiform when Make answers

1. Make accepts the request: the relay answers 204, and Ikiform marks the delivery succeeded.
2. Make is rate limited (429): the relay answers 503 with Make's Retry-After, and Ikiform waits that long before it tries again.
3. Make answers with any other error: the relay answers 502, and Ikiform retries, 8 retries over about 3 d 6 h.

On the verified route. On the direct route, Make answers Ikiform itself.

Without a **Webhook response** module, Make answers `200 Accepted` as soon as the request is queued, before the scenario runs. Ikiform counts that as delivered. If a later module fails, rerun it from Make's history; Ikiform won't retry it. If you add a Webhook response module, place it early, so Make answers within Ikiform's 15 seconds.

## Troubleshooting

| Problem | What to do |
| --- | --- |
| `400` with `Queue is full` | The scenario is off or slow, and Make's webhook queue filled up. Turn the scenario on; Ikiform retries the deliveries. |
| `502` with `Make answered 4xx` on the verified route | Make refused the request, most often because `MAKE_API_KEY` doesn't match the key on the webhook. Copy it again and update the relay. |
| New form fields don't show up in the mapping | Open the webhook module, choose **Redetermine data structure**, and send a submission that includes the new fields. |
| Endpoint disabled right after you deleted or changed the scenario | Ikiform disables an endpoint at once if it answers `410 Gone`, which can happen when nothing listens on a Make address anymore. Create a new webhook, update the endpoint URL, and turn **Enabled** back on. |
| `400 no_matching_signature` | The secret is wrong, or the body was parsed and re-serialized before verifying. Use the `whsec_` secret of this endpoint and the raw body exactly as received. After **Roll secret**, update it within 24 hours. |
| Delivery log shows `timeout` | Nothing answered within 15 seconds. Answer first and do slow work after, or make sure the tool responds right away. |
| 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. |

## Next steps

| Want to | Read |
| --- | --- |
| Add each submission as a sheet row | [Send form submissions to Google Sheets](https://ikiform.com/blog/form-submissions-to-google-sheets.md) |
| Verify inside the workflow tool itself | [Send form submissions to n8n](https://ikiform.com/blog/form-submissions-to-n8n.md) |
| See every header and retry rule | [Webhooks](https://ikiform.com/docs/webhooks.md) |

Make's own reference: [Webhooks](https://help.make.com/webhooks) and the [Webhooks app](https://apps.make.com/gateway).
