# Guide to Make Webhooks: Features and Best Practices

Make webhooks are the requests a Make scenario sends to your own systems. Make, formerly Integromat, is a no-code workflow automation platform, and when a scenario needs to hand data to a service you run, for example a new form submission, a CRM update or a row from a spreadsheet, the usual tool is the HTTP app's **Make a request** module pointed at your endpoint.

This guide covers how outbound Make requests are built, how to authenticate them with an API key keychain, how to give your receiver something to route on, and the best practices for running a Make receiver in production.

## What are Make webhooks?

Unlike most webhook providers, Make doesn't define what arrives at your endpoint. The person building the scenario does. In the **HTTP > Make a request** module they choose the URL, the method, the headers, the query parameters, the body format and the body itself, usually by mapping values from earlier modules in the scenario. Make adds no event name, no envelope and no signature.

That means two things for a receiver. First, the request has whatever shape the scenario gives it, so agree on a body format with whoever builds the scenario, and include a field that says what the event is. Second, authentication is something you add, using one of the module's authentication types.

The module supports **No authentication**, **API key**, **Basic Auth** and **OAuth 2.0**. Make recommends defining credentials in the module's dedicated **Credentials** field, which stores them in a keychain, rather than typing them into headers or query parameters. For a webhook receiver, an API key keychain placed in a header is the simplest choice.

## Make webhook features

| Feature | Details |
| --- | --- |
| Sender | The HTTP app's **Make a request** module in a scenario |
| URL | HTTPS only. Self-signed certificates are rejected |
| Methods | `GET`, `HEAD`, `POST`, `PUT`, `PATCH`, `DELETE` or `OPTIONS`, chosen per module |
| Body | JSON (a string or a data structure), `multipart/form-data`, `application/x-www-form-urlencoded`, or a custom content type |
| Event identifier | None from Make. Add one to the body yourself |
| Authentication | API key, Basic Auth or OAuth 2.0 credentials. No signature |
| API key keychain | Name, Key (sent exactly as entered), placement (header or query) and parameter name |
| Timeout | 1 to 300 seconds, set on the module |
| Redirects | Optionally followed, up to 10 |
| Errors | **Return error if HTTP request fails** stops the scenario on a `4xx` or `5xx` |
| Retries | No retry policy documented for the module |

## Common events

Make has no event catalogue for outbound requests, because every request is built in a scenario. The events are whatever your scenarios report: "lead created", "invoice paid", "row added". Put the name in the body, and keep the shape the same across scenarios so one receiver can handle them all:

```json
{
  "event": "lead.created",
  "occurred_at": "2026-10-22T09:15:00Z",
  "id": "{{1.id}}",
  "data": {
    "email": "{{1.email}}",
    "company": "{{1.company}}"
  }
}

```

The `{{1.id}}` placeholders stand for values mapped from earlier modules. With the **JSON string** input method, you're responsible for producing valid JSON. With **Data structure**, Make escapes JSON reserved characters in values for you, which avoids broken bodies when a mapped value contains a quote.

Include a stable `id`, taken from the record the scenario is handling, so the receiver can recognise a repeat.

## Setting up Make webhooks

1. Generate a long random value, for example with `openssl rand -hex 32`.
2. In your scenario, add **HTTP > Make a request** and set **Authentication type** to **API key**.
3. Under **Credentials**, create a keychain. Enter a name, put your value in **Key** (with a prefix such as `Bearer ` only if you want one, since the whole value is sent), set **API key placement** to **In the header**, and set **API key parameter name** to the header you want, such as `X-Make-Secret`.
4. Enter your HTTPS endpoint as the **URL**, choose the method, set **Body content type** to `application/JSON`, and build the body.
5. Set a **Timeout** that fits your endpoint, and decide whether **Return error if HTTP request fails** should stop the scenario.

You can edit a keychain later under **Keys**, and every module that uses it picks up the change, which makes rotation straightforward.

**In Hookdeck.** Create a source with the Make type. Enter the API key parameter name in **API Key Header** and the exact Key in **API Key**, including any prefix, and use the source URL as the module's URL. Hookdeck matches the header name case-insensitively and the value exactly. The source type pre-selects `POST`. If your module uses `GET`, `PUT`, `PATCH` or `DELETE`, allow that method on the source too. Don't use `OPTIONS`: Hookdeck answers it as a CORS preflight and never turns it into an event. A keychain placed in the query parameters, Basic Auth and OAuth 2.0 can't be checked by this source type.

## Securing Make webhooks

Compare the header the keychain sends with your stored value in constant time, and reject requests where it's missing or different.

```javascript
const crypto = require('crypto');
const express = require('express');

function safeEqual(received, expected) {
  const a = Buffer.from(received, 'utf8');
  const b = Buffer.from(expected, 'utf8');
  // timingSafeEqual throws on buffers of different lengths
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

const app = express();

app.post('/webhooks/make', express.raw({ type: '*/*' }), (req, res) => {
  const expected = process.env.MAKE_WEBHOOK_SECRET;
  if (!expected) return res.sendStatus(500); // fail closed when unconfigured

  const received = req.get(process.env.MAKE_WEBHOOK_HEADER || 'X-Make-Secret');
  if (typeof received !== 'string' || !safeEqual(received, expected)) {
    return res.sendStatus(401);
  }

  let payload;
  try {
    payload = JSON.parse(req.body.toString('utf8'));
  } catch {
    return res.sendStatus(400); // the scenario sent something other than JSON
  }
  // Route on payload.event, deduplicate on payload.id, process async.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

def verify_make_request(headers) -> bool:
    expected = os.environ.get("MAKE_WEBHOOK_SECRET")
    if not expected:
        return False  # fail closed
    header_name = os.environ.get("MAKE_WEBHOOK_HEADER", "X-Make-Secret")
    received = headers.get(header_name)  # Starlette and Flask headers are case-insensitive
    if not received:
        return False
    return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))

```

A matching header shows that the caller has the key. It doesn't show that the body is intact, and a captured request still passes if it's sent again. Make only calls HTTPS URLs, which protects the header in transit. Keep the key long and random, don't place it in query parameters, where it ends up in access logs, and rotate it in the keychain and your configuration if it may have leaked.

> **Make your Make scenarios production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the keychain header on every request, answers the module straight away, and queues each event durably with retries and replay.

## Make webhook limitations and pain points

### Every scenario invents its own payload

**The Problem:** Your receiver has to cope with as many body shapes as there are scenarios, and a scenario edit can change the shape without warning.

**Why It Happens:** Make sends whatever the module is configured to send. There's no fixed envelope or event field.

**Workarounds:**

* Agree on one body format, with `event`, `id` and `data`, and use a **Data structure** in the module so it's enforced.
* Validate the body on arrival and return `400` for anything that doesn't match, so the scenario's error shows up in Make.

**How Hookdeck Can Help:** Hookdeck stores every request with its full body, so you can see exactly what each scenario sends, and [transformations](/docs/transformations) can normalise different shapes into one before they reach your service.

### No retry policy of its own

**The Problem:** If your endpoint is down when the module runs, the data may never arrive.

**Why It Happens:** Make documents a timeout and an option to stop the scenario on a `4xx` or `5xx`, but no automatic retry policy for the module. What happens after a failure depends on the error handling built into the scenario.

**Workarounds:**

* Add error handling to the scenario around the HTTP module, and test what it does when your endpoint returns a `500`.
* Turn on **Return error if HTTP request fails** so failures are visible in the scenario's history rather than silently ignored.

**How Hookdeck Can Help:** Hookdeck accepts each request as soon as it arrives and stores it, then delivers it to your service with retries on a schedule you control, so an outage on your side doesn't depend on how each scenario handles errors.

### A static key, not a signature

**The Problem:** Anyone who learns the key can send requests that look like they came from your scenarios.

**Why It Happens:** An API key keychain sends the same value on every request. Make doesn't sign the body.

**Workarounds:**

* Use a long random key, header placement only, and a separate keychain per environment.
* Deduplicate on a stable `id` in the body, so a replayed request has no further effect.

**How Hookdeck Can Help:** Hookdeck's Make source type compares the header you name with the exact value you enter. Hookdeck answers the module with a `200` either way, then records a request without the right header as failed verification, so it never reaches your destination.

### Failures behind Hookdeck aren't visible in Make

**The Problem:** With Hookdeck in front of your endpoint, a scenario shows every run as successful, even when your service later rejects the event or the key is wrong.

**Why It Happens:** Hookdeck answers the module itself, before your service sees the request, so **Return error if HTTP request fails** never triggers for problems downstream.

**Workarounds:**

* Watch Hookdeck rather than the scenario history for delivery problems and failed verification.
* Send a test run after any change to the keychain.

**How Hookdeck Can Help:** Hookdeck [issues](/docs/issues) alert you when deliveries to your service fail, and every request, including those that failed verification, is listed with its headers and body.

## Best practices

### Use the keychain, not a raw header

Put the secret in an API key keychain rather than in the module's **Headers** field. Make recommends this because it avoids exposing keys in requests, centralises control of credentials, and makes rotation safe and easy.

### Send JSON with an event name and ID

Use **application/JSON** with a **Data structure**, and include `event` and a stable `id` in every body. Routing and deduplication both depend on them. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Keep one method per endpoint

Choose `POST` for event delivery unless you have a reason not to, and allow other methods on the receiver only where a scenario uses them.

### Acknowledge fast and process asynchronously

A slow response holds up the scenario until the module's timeout. Verify, store the event, return `200`, and do the work afterwards. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Make sends exactly the request your scenario builds, with an API key keychain header as the credential you add. Place the key in a header, compare it in constant time, give every body an `event` and an `id`, and plan your own error handling, because the module has no retry policy of its own.

[Hookdeck Event Gateway](https://hookdeck.com) sits in front of your receiver, checks the keychain header, answers the module at once, and keeps every event until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and receive Make scenario requests reliably in minutes.