# Set up a webhook

> Create a webhook endpoint, choose its events, add payload filters, send a test event, and enable, disable or rotate the secret.

This guide creates a webhook, narrows it to the events you need, and checks that your endpoint receives signed requests. You can do everything in the dashboard or with the [Webhooks API](/docs/api-reference/webhooks/).

## Before you begin

- A public URL that accepts `POST` requests. HTTPS is strongly recommended. URLs on `localhost` or private IP ranges are rejected; for local development, use a tunnel such as ngrok or Cloudflare Tunnel.
- An endpoint that keeps the raw request body so it can [verify the signature](/docs/webhooks/request-signature/).
- For the API, a key with **Full Access**.
- A free webhook slot. Pay as you go includes 3 endpoints, Pro 10, and Business and Custom 100.

## Create the webhook

**Dashboard**

  1. **Open Webhooks.** Go to **Email API → Webhooks** and select **Add webhook**.

  2. **Enter a name and URL.** The name must be unique in the workspace, for example `Production events`. The URL is your endpoint, for example `https://acme.com/webhooks/emailit`. Select **Create**.

  3. **Copy the secret.** The dialog shows the webhook secret, starting with `whsec_`, together with the warning "You can see the webhook secret only once. Store it safely." Copy it into your app's environment, for example as `EMAILIT_WEBHOOK_SECRET`, and select **Done**.

  A webhook created in the dashboard is subscribed to every event type. The webhook's **Settings** tab opens so you can narrow it.

**API**

  Call [Create a webhook](/docs/api-reference/webhooks/create/). List the event types in `events`, or set `all_events` to `true`. The `201` response includes the `secret`.

```bash
curl https://api.emailit.com/v2/webhooks \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Production events",
    "url": "https://acme.com/webhooks/emailit",
    "events": ["email.delivered", "email.bounced", "email.complained"]
  }'
```

  Unlike the dashboard, the API defaults to `all_events: false` and an empty `events` list, so a webhook created without either receives nothing. A duplicate name returns `409`; reaching your plan's endpoint limit returns `422` with `usage.used` and `usage.limit`.

## Choose events

A webhook receives either every event type or only the types you select.

**Dashboard**

  On the webhook's **Settings** tab, turn off **All events**, then select types in the **Events** card. Events are grouped by resource (**Emails**, **Domains**, **Audiences**, **Subscribers**, **Contacts**, **Templates**, **Suppressions**, **Email Verifications**, **Email Verification Lists**), and each group has a **Select all** box. Select **Save**.

  The picker doesn't list every type Emailit sends. `email.canceled`, `email.held`, `email.unsubscribed`, `email.resubscribed`, `subscriber.resubscribed` and the `campaign.*` events reach webhooks that have **All events** on, or you can add them to the list with the API. The **Deprecated** group holds old event names that are no longer sent. See [Event types](/docs/webhooks/event-types/).

**API**

  Call [Update a webhook](/docs/api-reference/webhooks/update/). `events` replaces the whole list. Setting `all_events` to `true` clears the list.

```bash
curl -X POST https://api.emailit.com/v2/webhooks/wh_2xGk8Hd3RvN6qT1mWsB9cL4pZ7e \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"all_events": false, "events": ["email.received", "email.held", "email.canceled"]}'
```

## Filter events by payload

Pro, Business, Custom

A payload filter delivers an event only when its data matches your rules. Filters apply on top of the event selection: an event must be subscribed *and* match the filter.

Use it to split traffic between endpoints, for example one webhook per product line, or to drop events you'd otherwise ignore in code.

- **Match mode:** **All rules match** (`all`) or **Any rule matches** (`any`).
- **Rules:** up to 25. Each rule has a field, an operator and a value.
- **Field:** a dotted path into the event's `data.object`, for example `to`, `status`, `meta.plan` or, for click events, `link.url`. A `payload.` prefix is optional, so `payload.from` and `from` are the same.
- **Comparisons are case-sensitive** and compare values as text, except `greater_than` and `less_than`, which compare numbers.

| Operator | Matches when the field |
| --- | --- |
| `equals` / `not_equals` | Is / isn't exactly the value. |
| `contains` / `not_contains` | Contains / doesn't contain the value. |
| `starts_with` / `ends_with` | Starts / ends with the value. |
| `greater_than` / `less_than` | Is a number greater / less than the value. |
| `is_set` / `is_not_set` | Has a non-empty value / is missing or empty. No value needed. |
| `in` / `not_in` | Equals / doesn't equal one of the values in an array. Send the array with the API, as in the example below. |

**Dashboard**

  On the webhook's **Settings** tab, find the **Filter** card. Choose the match mode, add rules with a field, operator and value, and select **Save**. Leave the rules empty to deliver every subscribed event. On Pay as you go, the card is locked and shows **Upgrade**.

**API**

  Send `filter` with [Create a webhook](/docs/api-reference/webhooks/create/) or [Update a webhook](/docs/api-reference/webhooks/update/). Set it to `null` to remove it. On Pay as you go, a filter returns `403` with `"error": "plan_required"`.

```bash
curl -X POST https://api.emailit.com/v2/webhooks/wh_2xGk8Hd3RvN6qT1mWsB9cL4pZ7e \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "filter": {
      "match": "all",
      "rules": [
        { "field": "to", "operator": "ends_with", "value": "@acme.com" },
        { "field": "meta.plan", "operator": "in", "value": ["pro", "business"] }
      ]
    }
  }'
```

More examples:

| Goal | Rule |
| --- | --- |
| Only mail received on one address | `to` `equals` `support@inbound.acme.com` |
| Only one sender domain | `from` `ends_with` `@billing.acme.com` |
| Only emails you labeled with metadata | `meta.source` `equals` `checkout` |
| Only clicks on your pricing page | `link.url` `starts_with` `https://acme.com/pricing` |
| Only emails that carry a customer ID | `meta.customer_id` `is_set` |

> **Fields differ between event types:** A rule on a field that an event doesn't have never matches. Email status events have `to` and `from` at the top level, but click and load events nest them as `email.rcpt_to` and `email.mail_from`, and contact events have `email`. With **All rules match**, a webhook filtered on `to` silently drops every click. Use separate webhooks per kind of event, or **Any rule matches** with one rule per shape. Check field names in the [event reference](/docs/webhooks/event-types/).

If a workspace moves to Pay as you go, existing filters stay saved but are ignored, and every subscribed event is delivered.

## Send a test event

A test posts one sample event to your URL immediately, signed with the webhook's current secret. Subscribed events and filters are ignored, the request isn't retried, and it doesn't appear on the **Requests** tab. Each webhook allows 5 tests per minute.

**Dashboard**

  On the webhook page, open the actions menu (**…**) and select **Send test**. Pick an event type and select **Send test**. The dialog shows "Your endpoint returned 200" (or the status your endpoint returned) and the response body. "Could not reach the endpoint" means the request didn't get an HTTP response, for example because of a DNS error, a timeout or a redirect.

**API**

  Call [Send a test event](/docs/api-reference/webhooks/test/) with any event type.

```bash
curl https://api.emailit.com/v2/webhooks/wh_2xGk8Hd3RvN6qT1mWsB9cL4pZ7e/test \
  -H "Authorization: Bearer $EMAILIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type": "email.delivered"}'
```

  The response has `ok`, `status_code`, `body` (your endpoint's response, up to 2,000 characters), `type` and the `payload` that was sent.

Test events use sample data, with an `event_id` that starts with `evt_test_`, and their shape can differ slightly from live events. Build your handler against the [event reference](/docs/webhooks/event-types/) and confirm with a real send.

## Enable or disable a webhook

Disable a webhook to stop deliveries without losing its settings, for example during maintenance.

- **Dashboard:** open the actions menu and select **Disable webhook** or **Enable webhook**. The status shows **Enabled** or **Disabled** on the webhook page.
- **API:** call [Update a webhook](/docs/api-reference/webhooks/update/) with `{"enabled": false}` or `{"enabled": true}`.

While a webhook is disabled, new events aren't queued for it, and requests already waiting for a retry are paused. Events that happen while it's disabled aren't delivered later; read them with the [Events API](/docs/logs/events/#reconcile-missed-webhook-events) if you need them. Emailit also disables webhooks automatically after 3 days of failures; see [Retries and failures](/docs/webhooks/retries-and-failures/).

Deleting a webhook drops all its pending events.

## Rotate the secret

Rotate the secret if it may have leaked, or as routine hygiene.

- **Dashboard:** open the actions menu, select **Webhook secret**, then **Reset**. The new secret is shown once.
- **API:** call [Rotate the signing secret](/docs/api-reference/webhooks/reset-secret/). The response contains the new `secret`. [Retrieve a webhook](/docs/api-reference/webhooks/get/) also returns the current secret.

The old secret stops working immediately, and every request from then on, including retries of older events, is signed with the new one. To rotate without rejecting requests, make your endpoint accept either secret for a few minutes, reset the secret, deploy the new value, then remove the old one.

## Verify it worked

1. Send a test event and confirm your endpoint returns `2xx`.
2. Send a real email, or trigger the event you subscribed to.
3. On the webhook's **Requests** tab, the request shows **Delivered**. The webhook's **Last used** time updates.

## Related

  - [Verify signatures](/docs/webhooks/request-signature/)
  - [Webhook requests](/docs/webhooks/webhook-requests/)
  - [Event types](/docs/webhooks/event-types/)
  - [Retries and failures](/docs/webhooks/retries-and-failures/)

---
Source: https://emailit.com/docs/webhooks/set-up/
