# Guide to Zapier Webhooks: Features and Best Practices

Zapier webhooks let a Zap send data to your own application. The **Webhooks by Zapier** action makes an HTTP request from any step of a Zap, so a new form response, CRM record or spreadsheet row can reach your API without a dedicated integration. Unlike most platforms in this series, Zapier doesn't sign these requests or define event types: you decide what is sent and how it is authenticated.

This guide covers sending webhooks from a Zap to an endpoint you control: the action's options, how to authenticate the request, and the best practices for receiving it in production. To go the other way, from an external service into Zapier, see [how to receive and replay external webhooks in Zapier with Hookdeck](/webhooks/platforms/how-to-receive-and-replay-external-webhooks-in-zapier-with-hookdeck).

## What are Zapier webhooks?

**Webhooks by Zapier** is a built-in Zapier app. As an action, it sends one HTTP request each time the Zap runs, with a body built from the Zap's earlier steps. The request goes to the URL you enter, with the method, payload format, headers and authentication you choose.

Because you build the request yourself, there is no fixed payload, no event name and no signature. Anything your receiver relies on, from a type field to a secret, has to be added in the action.

## Zapier webhook features

| Feature | Details |
| --- | --- |
| Action | Webhooks by Zapier |
| Methods | `GET`, `POST`, `PUT`, or **Custom Request** for `PATCH`, `DELETE` and anything that needs raw input |
| Payload types | Form (URL-encoded), JSON, XML, or Raw |
| Body | Key-value **Data** fields with `__` nesting, or raw text with Custom Request |
| Headers | **Headers** section for custom headers |
| Authentication | **Basic Auth** field, or a secret you add as a header |
| Signature | None |
| Event types | None; add your own field if you need one |
| Payload limit | 5MB for actions |
| Rate limits | Apply to Webhooks by Zapier |

## Common events

Zapier has no webhook events of its own. Each request carries whatever the Zap's Data section produces. With a templated action such as `POST`, these Data fields:

```text
order       | 1234
item__price | 19.99
item__sku   | SKU-1

```

become this JSON body. The `|` only separates the left and right fields in Zapier's editor, and in a real Zap the values would be mapped from earlier steps:

```json
{
  "order": "1234",
  "item": { "price": "19.99", "sku": "SKU-1" }
}

```

If you leave the Data section blank, Zapier sends every field from the previous step. That is handy for a quick test but means the body changes whenever the trigger app changes its output, so map fields explicitly for production.

When several Zaps post to the same endpoint, add a field such as `event` or `zap` to each one so your receiver can route them.

## Setting up Zapier webhooks

1. Add a **Webhooks by Zapier** action to your Zap and pick `POST` (or **Custom Request** if you need `PATCH`, `DELETE`, nested arrays or empty values).
2. Choose a **Payload Type**. JSON is the easiest to parse on the receiving side.
3. Enter your endpoint URL. Zapier recommends putting parameters in the Data fields rather than the URL.
4. Fill in the **Data** fields, mapping values from earlier steps.
5. Under **Headers**, add a secret header, for example `Authorization` with `Bearer <your-secret>`. Alternatively, use the **Basic Auth** field.
6. Test the step and check that your endpoint received the request.

## Securing Zapier webhooks

Zapier doesn't sign the request, so your endpoint has to check the credential you added in the action. Generate a long random secret, store it in the Zap's header and in your server's environment, and compare the two in constant time.

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

function verifyZapierSecret(headers, expected_value) {
  const received_value = headers['authorization'];
  if (typeof received_value !== 'string' || typeof expected_value !== 'string') return false;
  const received = Buffer.from(received_value, 'utf8');
  const expected = Buffer.from(expected_value, 'utf8');
  return received.length === expected.length && crypto.timingSafeEqual(received, expected);
}

const app = express();

app.post('/webhooks/zapier', express.raw({ type: '*/*' }), (req, res) => {
  const secret = process.env.ZAPIER_WEBHOOK_SECRET;
  if (!secret) return res.sendStatus(500); // fail closed

  if (!verifyZapierSecret(req.headers, `Bearer ${secret}`)) {
    return res.sendStatus(401);
  }

  // The body format depends on the action's Payload Type.
  const body = req.body.toString('utf8');
  // Parse as JSON, form data or XML to match the Zap, queue it, then respond.
  res.sendStatus(200);
});

```

The same check in Python:

```python
import hmac
import os

def verify_zapier_secret(headers, expected_value: str) -> bool:
    received_value = headers.get("authorization")
    if not received_value or not expected_value:
        return False
    return hmac.compare_digest(received_value.encode("utf-8"), expected_value.encode("utf-8"))

# In FastAPI:
# verify_zapier_secret(request.headers, f"Bearer {os.environ['ZAPIER_WEBHOOK_SECRET']}")

```

If you use the Basic Auth field instead, check the `Authorization: Basic ...` header against the credentials you configured, in the same constant-time way.

> **Make Zapier webhooks production-ready.** [Hookdeck Event Gateway](/event-gateway) checks the secret header from your Zap, queues every request, and lets you replay any run your service failed to process.

Hookdeck's Zapier source type checks a header that you name, such as `Authorization`, against the exact value you enter, including any prefix such as `Bearer `. The header name is matched case-insensitively and the value must match exactly. For Basic Auth, enter `Authorization` as the header and `Basic ` followed by the base64-encoded credentials as the value.

## Zapier webhook limitations and pain points

### No signature

**The Problem:** Anyone who learns your endpoint URL can send requests that look like they came from your Zap.

**Why It Happens:** Webhooks by Zapier sends a plain HTTP request. Zapier has no signing secret for this action, and its docs point to a private app on the Developer Platform for more advanced authentication.

**Workarounds:**

* Add a long random secret in a header and check it on every request.
* Use HTTPS so the header can't be read in transit, and rotate the secret if a Zap is shared or exported.

**How Hookdeck Can Help:** Hookdeck verifies the secret header and stops requests that fail before they reach your service, so your service only sees requests from your Zaps.

### The body is only as stable as your Zap

**The Problem:** A field your handler expects disappears or changes type.

**Why It Happens:** The body is built from earlier steps. A blank Data section forwards every field from the previous step, so a change in the trigger app changes the body. Custom Request sends exactly what you typed, without validation.

**Workarounds:**

* Map each field explicitly in the Data section.
* Validate the body against a schema in your handler and reject what doesn't match.
* Run Custom Request bodies through a JSON validator before turning the Zap on, as Zapier recommends.

**How Hookdeck Can Help:** Hookdeck keeps every request's headers and body, so you can see exactly what a Zap sent and replay it after you fix the mapping.

### Payload size and rate limits

**The Problem:** Large records or bursts of Zap runs fail.

**Why It Happens:** Webhooks by Zapier actions have a 5MB payload limit, and rate limits apply to the app.

**Workarounds:**

* Send IDs instead of whole records and fetch the details from your side.
* Keep your endpoint fast so a burst of Zap runs doesn't back up.

**How Hookdeck Can Help:** Hookdeck accepts the request immediately and delivers it to your service at a rate you set, so bursts from busy Zaps don't overload your API.

## Best practices

### Pick one payload type and parse for it

Zapier sends Form, JSON, XML or Raw depending on the action's setting. Read the raw body and parse it for the format you chose rather than relying on a framework parser that may not match.

### Add your own event field

Give each Zap a fixed field such as `event` so one endpoint can serve several Zaps and log which one sent a request.

### Make handlers idempotent

If you rerun a Zap or replay a request, the same data arrives again. Include a stable ID from the trigger app in the body and deduplicate on it. See our [guide to webhook idempotency](/webhooks/guides/implement-webhook-idempotency).

### Acknowledge fast and process asynchronously

Return a `2xx` once the request is stored and do the work in the background. See [why to process webhooks asynchronously](/webhooks/guides/why-implement-asynchronous-processing-webhooks).

## Conclusion

Webhooks by Zapier sends whatever your Zap builds, in the format and with the headers you choose. Because Zapier doesn't sign the request, add a secret header or Basic Auth and check it on every request, map the Data fields explicitly, and include your own event field and ID so the receiver can route and deduplicate.

[Hookdeck Event Gateway](https://hookdeck.com) checks your Zap's secret, queues every request, and holds it until your service has processed it.

[Get started with Hookdeck](https://dashboard.hookdeck.com/signup) for free and handle Zapier webhooks reliably in minutes.