> ## Documentation Index
> Fetch the complete documentation index at: https://pigeonpost-developer.27communication.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Get told about new mail and changes the moment they happen.

A webhook endpoint is a URL in your app. The engine sends it a `POST` with a JSON body whenever one of the events you chose happens.

## Create an endpoint

```bash theme={null}
curl -X POST "$EE_URL/api/v1/webhooks/endpoints" \
  -H "X-API-KEY: $EE_KEY" -H "Content-Type: application/json" \
  -d '{
    "url": "https://app.example.com/webhooks/email",
    "trigger_events": ["email.new", "email.update", "account.status.disconnected"],
    "description": "CRM sync",
    "headers": [{ "key": "Authorization", "value": "Bearer my-own-token" }]
  }'
```

<ParamField body="url" type="string" required>Your HTTPS endpoint.</ParamField>

<ParamField body="trigger_events" type="string[]" required>
  The events sent to this URL. At least one. See [Events](/webhooks/events) for the full list.
</ParamField>

<ParamField body="account_ids" type="string[]">Only events of these accounts (`acc_…`). Empty means every account.</ParamField>
<ParamField body="description" type="string">Your description. It isn't sent in events.</ParamField>
<ParamField body="headers" type="object[]">Extra headers sent with every call, as `{ "key", "value" }`.</ParamField>
<ParamField body="enabled" type="boolean" default="true">`false` creates it paused.</ParamField>

```json Response theme={null}
{
  "object": "WebhookEndpoint",
  "id": "we_e3f1a2b4c5d64e7f8a9b0c1d2e3f4a5b",
  "url": "https://app.example.com/webhooks/email",
  "description": "CRM sync",
  "trigger_events": ["email.new", "email.update", "account.status.disconnected"],
  "account_ids": [],
  "headers": [{ "key": "Authorization", "value": "Bearer my-own-token" }],
  "secret": "whsec_Zq3Rk8Tn2Vb6Xy1Lm4Pw7Hs9Jd5Fg0",
  "enabled": true,
  "created_at": "2026-10-06T08:00:00Z"
}
```

`secret` signs every call to this endpoint. Store it and use it to [verify signatures](/webhooks/signatures). It's also returned when you get or list the endpoint.

<Warning>
  Treat `secret` like a password. Anyone who has it can forge calls that pass your signature check.
</Warning>

## Manage endpoints

| Request | |
| - | - |
| `GET /api/v1/webhooks/endpoints` | List endpoints, newest first. Paged with `offset` and `limit` ([pagination](/reference/pagination)) |
| `GET /api/v1/webhooks/endpoints/{we_id}` | One endpoint |
| `PATCH /api/v1/webhooks/endpoints/{we_id}` | Change it: send only the fields to change. It keeps its `id` and `secret` |
| `DELETE /api/v1/webhooks/endpoints/{we_id}` | Delete it. Calls still waiting to be sent are dropped. Returns `{ "success": true }` |

### Change an endpoint

```bash theme={null}
curl -X PATCH "$EE_URL/api/v1/webhooks/endpoints/we_e3f1a2b4c5d64e7f8a9b0c1d2e3f4a5b" \
  -H "X-API-KEY: $EE_KEY" -H "Content-Type: application/json" \
  -d '{ "url": "https://app.example.com/webhooks/email-v2", "trigger_events": ["email.new"] }'
```

```json Response theme={null}
{
  "object": "WebhookEndpoint",
  "id": "we_e3f1a2b4c5d64e7f8a9b0c1d2e3f4a5b",
  "url": "https://app.example.com/webhooks/email-v2",
  "trigger_events": ["email.new"],
  "secret": "whsec_Zq3Rk8Tn2Vb6Xy1Lm4Pw7Hs9Jd5Fg0",
  "enabled": true,
  "…": "…"
}
```

* Fields you don't send keep their value. `headers`, `trigger_events` and `account_ids` replace the whole list.
* Send `"enabled": false` to pause it, and `"enabled": true` to resume. Calls waiting while it's paused are dropped.

Endpoints can also be managed in your dashboard under **Webhooks**.

## Delivery log

Every call to an endpoint is a **conversation** (`whc_…`). List them to see what was sent and what your endpoint answered:

```bash theme={null}
curl "$EE_URL/api/v1/webhooks/conversations?endpoint_id=we_e3f1a2b4c5d64e7f8a9b0c1d2e3f4a5b&limit=20" \
  -H "X-API-KEY: $EE_KEY"
```

<ParamField query="endpoint_id" type="string" required>The endpoint (`we_…`).</ParamField>
<ParamField query="event_id" type="string">Only the calls of this event (`evt_…`).</ParamField>
<ParamField query="limit" type="integer" default="50">1 to 250.</ParamField>
<ParamField query="offset" type="integer" default="0">How many calls to skip.</ParamField>

```json Response theme={null}
{
  "object": "WebhookConversations",
  "data": [
    {
      "object": "WebhookConversation",
      "id": "whc_5a6b7c8d9e0f41a2b3c4d5e6f7a8b9c0",
      "endpoint_id": "we_e3f1a2b4c5d64e7f8a9b0c1d2e3f4a5b",
      "event_id": "evt_7c1d2e3f4a5b46c7d8e9f0a1b2c3d4e5",
      "event": "email.new",
      "status": "failed",
      "attempts": 8,
      "last_status_code": 500,
      "last_error": "HTTP 500 Internal Server Error",
      "payload": { "object": "Event", "id": "evt_7c1d2e3f4a5b46c7d8e9f0a1b2c3d4e5", "type": "email.new", "…": "…" },
      "created_at": "2026-10-06T09:12:03Z",
      "updated_at": "2026-10-06T10:15:41Z"
    }
  ],
  "has_more": false
}
```

| Field | |
| - | - |
| `status` | `pending` (waiting to be sent or retried), `succeeded` or `failed` |
| `attempts` | How many times it was tried. See [Retries](/webhooks/retries) |
| `last_status_code` | Your endpoint's last HTTP status, or `null` when it didn't answer |
| `last_error` | The last error, or `null` |
| `payload` | The exact body that was sent |

Your dashboard shows the same log under **Webhooks**.

## Your endpoint

* Answer with any **2xx** status, quickly (within 10 seconds). Do the slow work afterwards, for example in a queue.
* Anything else, or no answer, is [retried](/webhooks/retries).
* An event can arrive more than once. Use the event `id` (`evt_…`) to skip duplicates.
* Events can arrive out of order. When order matters, fetch the email again to get its current state.
* URLs that resolve to private or local addresses (`10.x`, `192.168.x`, `localhost`) are refused, and redirects aren't followed.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.