---
title: "Webhook events and stats"
description: "The events an endpoint can subscribe to, and how the deliveries went."
url: "https://nextmail.sh/docs/api/webhooks/events"
area: "API"
category: "Keys & webhook logs"
---

# Webhook events and stats

The events an endpoint can subscribe to, and how the deliveries went.

**Also documents:** `GET /webhooks/events`, `GET /webhooks/stats`

## Events

`GET /webhooks/events` lists every event an endpoint can name in `eventTypes`, each with a sentence saying when it fires, and the limits an endpoint is held to: `maxEndpoints` for the workspace, which its plan decides, and `maxAddresses` and `maxDomains` for one allowlist. It needs `webhooks:read`.

**GET /webhooks/events**

```
{
  "object": "webhook_catalogue",
  "events": [
    { "id": "email.received", "label": "A message arrives in this mailbox" },
    { "id": "email.sent", "label": "A message goes out, however it was sent" },
    { "id": "contact.unsubscribed", "label": "Someone unsubscribes from one of your audiences" },
    { "id": "sending.paused", "label": "A sending lane of the workspace is paused" }
  ],
  "maxEndpoints": 10,
  "maxAddresses": 50,
  "maxDomains": 25
}
```

> An endpoint that names no events receives every email event except `email.replied`, so `email.replied` and the other families reach only an endpoint that asks for them by name. The list leaves out `automation.webhook`, which no endpoint subscribes to: a webhook step of an automation sends it to the endpoint the step names.

## Stats

`GET /webhooks/stats` reads how the deliveries went inside a window, the Analytics tab of the Webhooks page: attempts, delivered and failed, the median time a receiver took to answer, a series of buckets, the events sent and the response codes received. It needs `webhooks:read`. `endpointIds` narrows it to some endpoints, `since` and `until` set the window, 30 days by default, and `grain` and `offsetMinutes` shape the series. Each try of an event counts as one attempt.

**GET /webhooks/stats?grain=day**

```
{
  "object": "webhook_stats",
  "since": "2026-09-02T00:00:00.000Z",
  "until": "2026-10-02T00:00:00.000Z",
  "grain": "day",
  "endpointIds": [],
  "totals": { "attempts": 1280, "delivered": 1266, "failed": 14, "medianDurationMs": 212 },
  "buckets": [{ "bucket": "2026-09-28", "delivered": 44, "failed": 1 }],
  "events": [{ "id": "email.delivered", "count": 702 }],
  "codes": [{ "id": "200", "count": 1266 }, { "id": "503", "count": 14 }]
}
```

## Payloads

Every delivery is a POST whose JSON body is `{ id, type, createdAt, data }`. `id` stays the same on every retry and every replay of one event, so keep the ids you have handled and skip one you have seen. `data` has the shape of its event, listed below and in the `webhooks` section of `/openapi.json`, and the TypeScript and Python SDKs type every one of them. Fields can be added to `data`, never renamed or taken away, so ignore the ones you do not know.

**email.received**

```
{
  "id": "evt_4f1c9a2e7b3d4c5e8f9a0b1c",
  "type": "email.received",
  "createdAt": "2026-10-14T09:12:40.118Z",
  "data": {
    "emailId": "msg_7c2e9a41b3d84f6e9a0c1d2e",
    "threadId": "thr_2b8e4c1a9d7f4e3b8a6c5d0e",
    "messageId": "<CAHk7pQ2x9LmZ4@mail.example.com>",
    "from": "ada@example.com",
    "fromName": "Ada Lovelace",
    "date": "2026-10-14T09:12:31.000Z",
    "subject": "The invoice for October",
    "to": "billing@acme.com",
    "attachments": [
      { "id": "msg_7c2e9a41b3d84f6e9a0c1d2e-1", "filename": "invoice.pdf", "mimeType": "application/pdf", "size": 48213 }
    ],
    "tags": {},
    "spam": false,
    "encryption": null
  }
}
```

| Event | Fields in `data` | SDK type |
| --- | --- | --- |
| `email.received` | `emailId`, `threadId`, `messageId`, `from`, `fromName`, `date`, `subject`, `to`, `attachments`, `tags`, `spam`, `muted`, `encryption`, `rules` | `EmailReceivedEventData` |
| `email.replied` | `emailId`, `threadId`, `messageId`, `from`, `fromName`, `date`, `subject`, `to`, `tags`, `repliesTo` | `EmailRepliedEventData` |
| `email.sent` | `emailId`, `from`, `tags`, `transport`, `messageId`, `threadId`, `subject`, `trackedMessageId`, `source`, `simulated`, `test`, `note` | `EmailSentEventData` |
| `email.failed` | `emailId`, `from`, `tags`, `error`, `stage`, `partial`, `recipients`, `updated` | `EmailFailedEventData` |
| `email.cancelled` | `emailId`, `from`, `tags` | `EmailCancelledEventData` |
| `email.scheduled` | `emailId`, `from`, `tags`, `source`, `recipients` | `EmailAcceptedEventData` |
| `email.queued` | `emailId`, `from`, `tags`, `source`, `recipients` | `EmailAcceptedEventData` |
| `email.delivered` | `emailId`, `from`, `tags`, `recipients`, `updated`, `smtpResponse` | `EmailDeliveredEventData` |
| `email.delivery_delayed` | `emailId`, `from`, `tags`, `recipients`, `updated`, `delayType`, `expirationTime`, `diagnostic` | `EmailDeliveryDelayedEventData` |
| `email.bounced` | `emailId`, `from`, `tags`, `recipients`, `updated`, `bounceType`, `bounceSubType`, `permanent`, `suppressed`, `diagnostic`, `threadId`, `messageId`, `fromName`, `date`, `subject`, `to`, `attachments`, `spam`, `muted`, `encryption`, `rules`, `outcome`, `status`, `correlated` | `EmailBouncedEventData`, `MailboxBounceEventData` |
| `email.complained` | `emailId`, `from`, `tags`, `recipients`, `updated`, `complaintFeedbackType`, `suppressed` | `EmailComplainedEventData` |
| `email.suppressed` | `emailId`, `from`, `tags`, `recipients`, `reason` | `EmailSuppressedEventData` |
| `email.opened` | `emailId`, `trackedMessageId`, `threadId`, `messageId`, `subject`, `from`, `tags`, `recipient`, `attributed`, `first`, `client`, `device`, `os`, `userAgent`, `ip`, `network`, `country`, `region`, `city`, `timezone`, `via`, `openCount`, `openedAt` | `EmailOpenedData` |
| `email.clicked` | `emailId`, `trackedMessageId`, `threadId`, `messageId`, `subject`, `from`, `tags`, `recipient`, `attributed`, `first`, `client`, `device`, `os`, `userAgent`, `ip`, `network`, `country`, `region`, `city`, `timezone`, `via`, `linkId`, `url`, `label`, `clickCount`, `clickedAt` | `EmailClickedData` |
| `email.downloaded` | `emailId`, `trackedMessageId`, `threadId`, `messageId`, `subject`, `from`, `tags`, `recipient`, `attributed`, `first`, `client`, `device`, `os`, `userAgent`, `ip`, `network`, `country`, `region`, `city`, `timezone`, `via`, `shareId`, `fileId`, `filename`, `mimeType`, `sizeBytes`, `url`, `downloadCount`, `downloadedAt` | `EmailDownloadedData` |
| `domain.verified` | `domainId`, `domain`, `verifiedAt`, `records`, `sendingState` | `DomainVerifiedEventData` |
| `domain.sending_changed` | `domainId`, `domain`, `previousState`, `state`, `changedAt` | `DomainSendingChangedEventData` |
| `domain.deleted` | `domainId`, `domain`, `reason`, `deletedAt` | `DomainDeletedEventData` |
| `suppression.added` | `email`, `reason`, `detail`, `addedAt`, `removedAt` | `SuppressionEventData` |
| `suppression.removed` | `email`, `reason`, `detail`, `addedAt`, `removedAt` | `SuppressionEventData` |
| `file.uploaded` | `fileId`, `filename`, `mimeType`, `sizeBytes`, `direction`, `to`, `threadId`, `messageId`, `uploadedAt`, `deletedAt` | `FileEventData` |
| `file.deleted` | `fileId`, `filename`, `mimeType`, `sizeBytes`, `direction`, `to`, `threadId`, `messageId`, `uploadedAt`, `deletedAt` | `FileEventData` |
| `form.submitted` | `formId`, `formName`, `submissionId`, `email`, `status`, `answers`, `audienceIds`, `sourceUrl`, `submittedAt` | `FormSubmittedEventData` |
| `form.confirmed` | `formId`, `formName`, `submissionId`, `email`, `audienceIds`, `via`, `confirmedAt` | `FormConfirmedEventData` |
| `automation.entered` | `automationId`, `enrollmentId`, `contactId`, `source`, `enteredAt` | `AutomationEnteredEventData` |
| `automation.exited` | `automationId`, `enrollmentId`, `contactId`, `reason`, `stepKey`, `exitedAt` | `AutomationExitedEventData` |
| `automation.paused` | `automationId`, `reason`, `detail` | `AutomationPausedEventData` |
| `automation.resumed` | `automationId`, `resumedAt` | `AutomationResumedEventData` |
| `automation.webhook` | `automationId`, `enrollmentId`, `stepKey`, `stepName`, `contact`, `data` | `AutomationWebhookEventData` |
| `contact.created` | `contactId`, `email`, `name`, `source`, `createdAt`, `updatedAt`, `deletedAt` | `ContactEventData` |
| `contact.updated` | `contactId`, `email`, `name`, `source`, `createdAt`, `updatedAt`, `deletedAt` | `ContactEventData` |
| `contact.deleted` | `contactId`, `email`, `name`, `source`, `createdAt`, `updatedAt`, `deletedAt` | `ContactEventData` |
| `contact.unsubscribed` | `contactId`, `email`, `audienceIds`, `broadcastId`, `automationId`, `unsubscribedAt`, `resubscribedAt` | `ContactSubscriptionEventData` |
| `contact.resubscribed` | `contactId`, `email`, `audienceIds`, `broadcastId`, `automationId`, `unsubscribedAt`, `resubscribedAt` | `ContactSubscriptionEventData` |
| `sending.paused` | `stream`, `reason`, `recipients`, `bounced`, `complained`, `pausedAt` | `SendingPausedEventData` |
| `sending.resumed` | `stream`, `resumedAt` | `SendingResumedEventData` |
| `connector.connected` | `id`, `app`, `appName`, `scope`, `access`, `connectedAt`, `disconnectedAt`, `reason`, `at` | `ConnectorEventData` |
| `connector.disconnected` | `id`, `app`, `appName`, `scope`, `access`, `connectedAt`, `disconnectedAt`, `reason`, `at` | `ConnectorEventData` |
| `connector.needs_attention` | `id`, `app`, `appName`, `scope`, `access`, `connectedAt`, `disconnectedAt`, `reason`, `at` | `ConnectorEventData` |
| `task.run.completed` | `runId`, `taskId`, `taskName`, `trigger`, `threadId`, `status`, `steps`, `actions`, `error`, `createdAt`, `finishedAt` | `TaskRunEventData` |
| `task.run.failed` | `runId`, `taskId`, `taskName`, `trigger`, `threadId`, `status`, `steps`, `actions`, `error`, `createdAt`, `finishedAt` | `TaskRunEventData` |
| `task.run.cancelled` | `runId`, `taskId`, `taskName`, `trigger`, `threadId`, `status`, `steps`, `actions`, `error`, `createdAt`, `finishedAt` | `TaskRunEventData` |
| `approval.requested` | `approvalId`, `runId`, `taskId`, `taskName`, `tool`, `expiresAt`, `requestedAt` | `ApprovalRequestedEventData` |
| `booking.created` | `bookingId`, `pageId`, `status`, `startAt`, `endAt`, `guestName`, `guestEmail`, `guestTimezone`, `location`, `rescheduleCount`, `endedBy`, `reason`, `createdAt`, `at` | `BookingEventData` |
| `booking.confirmed` | `bookingId`, `pageId`, `status`, `startAt`, `endAt`, `guestName`, `guestEmail`, `guestTimezone`, `location`, `rescheduleCount`, `endedBy`, `reason`, `createdAt`, `at` | `BookingEventData` |
| `booking.rescheduled` | `bookingId`, `pageId`, `status`, `startAt`, `endAt`, `guestName`, `guestEmail`, `guestTimezone`, `location`, `rescheduleCount`, `endedBy`, `reason`, `createdAt`, `at` | `BookingEventData` |
| `booking.cancelled` | `bookingId`, `pageId`, `status`, `startAt`, `endAt`, `guestName`, `guestEmail`, `guestTimezone`, `location`, `rescheduleCount`, `endedBy`, `reason`, `createdAt`, `at` | `BookingEventData` |
| `broadcast.sending` | `broadcastId`, `status`, `subject`, `fromEmail`, `audienceIds`, `recipients`, `created`, `skipped`, `failed`, `error`, `at` | `BroadcastEventData` |
| `broadcast.sent` | `broadcastId`, `status`, `subject`, `fromEmail`, `audienceIds`, `recipients`, `created`, `skipped`, `failed`, `error`, `at` | `BroadcastEventData` |
| `broadcast.failed` | `broadcastId`, `status`, `subject`, `fromEmail`, `audienceIds`, `recipients`, `created`, `skipped`, `failed`, `error`, `at` | `BroadcastEventData` |
| `broadcast.cancelled` | `broadcastId`, `status`, `subject`, `fromEmail`, `audienceIds`, `recipients`, `created`, `skipped`, `failed`, `error`, `at` | `BroadcastEventData` |

> `email.bounced` comes in two shapes. A bounce reported for a send of yours names the send by `emailId` and lists `recipients`, `bounceType` and `suppressed`. A delivery report that arrives at one of your addresses is filed like other mail, so it carries the fields of `email.received` with `outcome`, `status`, `correlated` and `recipients` added. Tell them apart by `outcome`, which only the second has.

## Reference

- [`GET /webhooks/events`](https://nextmail.sh/docs/api/reference/webhooks#get-webhooks-events): full reference
- [`GET /webhooks/stats`](https://nextmail.sh/docs/api/reference/webhooks#get-webhooks-stats): full reference
