---
title: Webhook events
description: Normalized EmailEvent and DeliveryEvent fields from provider webhook parsers.
icon: Webhook
source: "src/webhooks/types.ts"
---

Email parsers return `EmailEvent[]`. SMS and WhatsApp parsers return `DeliveryEvent[]`.
Use `toDeliveryEvent` to put email events on the same ingest path as SMS / WhatsApp.

<Callout title="The one rule">
  Branch on `event.type` and keep provider-specific details in `event.raw` when you need them.
</Callout>

## DeliveryEvent

| Field | Type | Meaning |
| ----- | ---- | ------- |
| `channel` | `"email" \| "sms" \| "whatsapp" \| "push"` | Messaging channel |
| `provider` | `string` | Parser id (e.g. `"twilio-sms"`) |
| `type` | union below | Normalized lifecycle type |
| `messageId` | `string?` | Provider message id when present |
| `recipient` | `string?` | Address or phone when present |
| `timestamp` | `Date?` | Event time when present |
| `raw` | `unknown` | Original provider payload |

## EmailEvent

| Field | Type | Meaning |
| ----- | ---- | ------- |
| `provider` | `string` | Parser id (e.g. `"sndr"`, `"resend"`). |
| `type` | union below | Normalized lifecycle type. |
| `messageId` | `string?` | Provider message id when present. |
| `recipient` | `string?` | Primary recipient when present. |
| `timestamp` | `Date?` | Event time when present. |
| `raw` | `unknown` | Original provider payload. |

## Normalized `type` values

| Value | Channels | Meaning |
| ----- | -------- | ------- |
| `queued` | SMS | Accepted / queued by the provider |
| `sent` | SMS / WhatsApp | Handed to the network |
| `delivered` | All | Delivered (or mailbox accepted for email) |
| `failed` | SMS / WhatsApp | Permanent failure |
| `read` | SMS / WhatsApp | Read receipt |
| `bounced` | Email | Hard failure or bounce |
| `complained` | Email | Spam complaint |
| `opened` | Email | Open tracking |
| `clicked` | Email | Click tracking |
| `deferred` | Email | Temporary deferral |
| `unknown` | All | Unmapped or informational event |

## Mapping helpers

```ts
import { toDeliveryEvent } from "sently/webhooks";

const delivery = toDeliveryEvent(emailEvent);
```

## SNDR mapping

| SNDR `type` | Normalized `type` |
| ----------- | ----------------- |
| `email.delivered` | `delivered` |
| `email.bounced` | `bounced` |
| `email.unsubscribed` | `unknown` |

SNDR `data.email_id` becomes `messageId`. Recipients come from `data.to` or `data.addresses`.

## Troubleshooting

<Accordions>

<Accordion title="Why is unsubscribed mapped to unknown?">

The shared email `type` union has no unsubscribe value. Keep the vendor payload in `raw` if you need unsubscribe-specific handling.

</Accordion>

</Accordions>

## Learn more

- [Webhooks guide](/docs/guides/webhooks) — verify + parse flow
- [Channel send result](/docs/reference/channel-result) — send-time accepted mapping

## Next

<Cards>
  <Card title="Webhooks" description="Parse and verify provider payloads." href="/docs/guides/webhooks" />
  <Card title="Exports" description="Package entrypoints." href="/docs/reference/exports" />
</Cards>
