> ## Documentation Index
> Fetch the complete documentation index at: https://docs.emailx.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Receive operation outcomes with webhooks

> Register an HTTPS endpoint, verify signed deliveries and recover failed ones.

Durable operations such as imports, launches, replies and warmup changes finish in a worker. A webhook endpoint receives a signed event when one of them reaches an outcome, so you do not have to poll `get_api_operation`. Webhooks need the `webhooks:read` and `webhooks:manage` scopes, an owner or admin identity and a host with webhooks enabled. A disabled host answers `503 webhooks_unavailable`.

## Register an endpoint

Call [create\_api\_webhook\_endpoint](https://docs.emailx.ai/en/api-reference/webhooks/create-webhook.md) with an `https://` URL and the event types you want. The URL must resolve only to public internet addresses; Emailx checks it when you save it and again before every delivery, and never follows redirects. Saving does not contact the URL.

The response contains `secret` once. Store it in your secret manager: Emailx never returns it again. If you lose it, [rotate the secret](https://docs.emailx.ai/en/api-reference/webhooks/rotate-webhook-secret.md).

| Event type | Sent when an operation becomes |
| - | - |
| `operation.succeeded` | succeeded |
| `operation.partially_succeeded` | partially succeeded |
| `operation.failed` | failed |
| `operation.cancelled` | cancelled |
| `operation.needs_reconciliation` | uncertain, and needs a person to check before anything is repeated |
| `operation.awaiting_user_action` | blocked until someone acts in Emailx |

Events carry the operation ID, kind, status, error code and resource, never the operation result. Read details with [get\_api\_operation](https://docs.emailx.ai/en/api-reference/jobs/get-job.md).

## Verify each delivery

Every delivery is a `POST` with a JSON body and three headers:

* `webhook-id`: the event ID. It stays the same on retries and redeliveries.
* `webhook-timestamp`: Unix seconds when this attempt was signed.
* `webhook-signature`: one or more `v1,<base64>` values separated by spaces.

The signature is HMAC-SHA256 over `{webhook-id}.{webhook-timestamp}.{raw body}`, keyed with the base64 bytes after `whsec_`. Reject a timestamp more than five minutes from your clock, and accept the request if any `v1` value matches.

```python theme={null}
import base64, hashlib, hmac, time

def verify(secret, headers, body: bytes) -> bool:
    ts = int(headers["webhook-timestamp"])
    if abs(time.time() - ts) > 300:
        return False
    key = base64.b64decode(secret.removeprefix("whsec_"))
    signed = f'{headers["webhook-id"]}.{ts}.'.encode() + body
    expected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()
    return any(hmac.compare_digest(expected, part[3:])
               for part in headers["webhook-signature"].split() if part.startswith("v1,"))
```

Answer with any `2xx` within 10 seconds and do the work afterwards. Deliveries can arrive more than once, so store `webhook-id` and ignore repeats.

## Retries and recovery

A failed attempt (non-`2xx`, timeout or unsafe destination) is retried after 1 minute, 5 minutes, 30 minutes, 2 hours, 8 hours and 24 hours. After seven attempts the delivery is `abandoned`. After 50 failed attempts in a row the endpoint switches itself off with `disabled_reason: too_many_failures`; switch it on again with [update\_api\_webhook\_endpoint](https://docs.emailx.ai/en/api-reference/webhooks/update-webhook.md).

* [Send a test event](https://docs.emailx.ai/en/api-reference/webhooks/test-webhook.md) to check a new endpoint.
* [List deliveries](https://docs.emailx.ai/en/api-reference/webhooks/list-webhook-deliveries.md) by `status` to find failures, and [read one](https://docs.emailx.ai/en/api-reference/webhooks/get-webhook-delivery.md) for its payload and last result.
* [Redeliver](https://docs.emailx.ai/en/api-reference/webhooks/redeliver-webhook.md) a finished delivery once your receiver is fixed. It keeps the same `webhook-id`.
* [Rotate the secret](https://docs.emailx.ai/en/api-reference/webhooks/rotate-webhook-secret.md) if it leaks. The old secret keeps signing for 24 hours alongside the new one, so update your receiver within that window.

From the CLI, the same actions live under `emailx webhooks`. MCP tools are `create_webhook_endpoint`, `send_webhook_test`, `list_webhook_deliveries` and `redeliver_webhook`, among others.


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