# WhatsApp events

Every WhatsApp message emits **events** as it moves through its lifecycle. Each event is a stamped fact about the message: when Bird accepted it, when it reached WhatsApp, when it was delivered, when the recipient read it, or that it failed. Events are how you follow what happened to a message after the send returns `202`.

## The event envelope

Events reach you in two shapes that carry the same lifecycle facts. A webhook delivery is the standard envelope described in the [Webhooks guide](/docs/guides/webhooks): a `type`, a `timestamp`, and a type-specific `data` object.

```json
{
  "data": {
    "direction": "outbound",
    "from": { "phone_number": "+13124495569" },
    "metadata": { "session_id": "sess_4821" },
    "tags": [{ "name": "flow", "value": "login-otp" }],
    "to": { "phone_number": "+14155550100" },
    "whatsapp_id": "wam_01ky7qbvswf3fvyaw3az90391c",
    "workspace_id": "ws_01ky7m235keycbnwyajabe1a6b"
  },
  "timestamp": "2026-07-23T14:51:39.913Z",
  "type": "whatsapp.delivered"
}
```

Every `whatsapp.*` payload's `data` carries the identity base: `whatsapp_id`, `workspace_id`, the message `direction` (`outbound` for messages you send), the `from` and `to` addresses (each a `phone_number` in E.164 and/or a Meta business-scoped user ID `bsuid`), and the `tags` and `metadata` from the send request (both `null` when the message carried none).

The [events API](#reading-events-from-the-api) returns the same lifecycle as leaner records: each event is an `id`, a `type`, and the `occurred_at` timestamp, since the message identity is already fixed by the request URL.

## Lifecycle events

A message emits events in order as it progresses. Not every message emits every type: a failed send stops at [`whatsapp.failed`](#failure-events), and `whatsapp.read` only appears if the recipient opens the message.

| Event                | Meaning                                                          |
| -------------------- | ---------------------------------------------------------------- |
| `whatsapp.accepted`  | Bird accepted the send request. This is what the `202` reported. |
| `whatsapp.sent`      | Handed to the WhatsApp network.                                  |
| `whatsapp.delivered` | Delivery confirmed to the recipient's device.                    |
| `whatsapp.read`      | The recipient opened the message.                                |

One thing about `whatsapp.read`: it does **not** change the message `status`. A delivered message stays `delivered`, and the read is recorded as a `read_at` timestamp on the message alongside the event.

The event type list is open: new types may be added over time, so treat an unrecognized value as a future event rather than an error.

## Failure events

`whatsapp.failed` is terminal: the message will not be delivered, and no further events follow. It is the one event type that carries an `error` object with the failure detail: a Bird-stable `code` (for example `undeliverable` or `insufficient_balance`), a human-readable `description`, an optional raw `meta_error_code` from the WhatsApp Cloud API, and the failure's `occurred_at`. The same object appears on the API record and in the webhook payload, and is absent on every other event type.

## Reading events from the API

`GET /v1/whatsapp/messages/{message_id}/events` returns a message's events in chronological order. The timeline is bounded and returned in full, so the list is not paginated. Reading events needs an API key with the `whatsapp_messages` scope:

```bash
curl https://us1.platform.bird.com/v1/whatsapp/messages/wam_.../events \
  -H "Authorization: Bearer $BIRD_API_KEY"
```

A message that was accepted, sent, delivered, and read returns four events:

```json
{
  "data": [
    {
      "id": "ev_01ky7q6a1fejfbvs0myn41hj41",
      "occurred_at": "2026-07-23T14:48:34.71Z",
      "type": "whatsapp.accepted"
    },
    {
      "id": "ev_01ky7q6a2denvtd6jg1vqwmg13",
      "occurred_at": "2026-07-23T14:48:35.671Z",
      "type": "whatsapp.sent"
    },
    {
      "id": "ev_01ky7q6a2zff9r2qm74mmg1g6z",
      "occurred_at": "2026-07-23T14:48:36.642Z",
      "type": "whatsapp.delivered"
    },
    {
      "id": "ev_01ky7q6c21frssf0vj8h50qysw",
      "occurred_at": "2026-07-23T14:48:38.65Z",
      "type": "whatsapp.read"
    }
  ]
}
```

To keep only one type, pass the `type` query parameter (for example `?type=whatsapp.failed`); omit it for the full timeline.

The same timeline is what the [WhatsApp log](/docs/guides/whatsapp/message-log) page renders when you open a message.

![The WhatsApp message detail sheet in the Bird dashboard, opened for a delivered bird_order_confirmation message: the Events tab showing the per-message lifecycle timeline of Accepted, Sent, Delivered, and Read, each with its timestamp, over the dimmed message list](/images/docs/dashboard-whatsapp-detail.png)

## Webhooks

The same five lifecycle events are delivered to your endpoints as webhooks, so you can react to a message the moment it moves instead of polling the events API. Subscribe an endpoint to any `whatsapp.*` type from the [**Webhooks**](https://bird.com/dashboard/w/webhooks) page in the dashboard or through the webhooks API; each delivery uses [the event envelope](#the-event-envelope) with the identity base and, on `whatsapp.failed`, the `error` object. The [Webhooks guide](/docs/guides/webhooks) covers creating endpoints, verifying signatures, and retries.

## Next steps

- [WhatsApp log](/docs/guides/whatsapp/message-log): the per-message view that renders this timeline
- [Sending WhatsApp messages](/docs/guides/whatsapp/sending-whatsapp): where a message's lifecycle begins
- [Webhooks guide](/docs/guides/webhooks): endpoints, signatures, retries, and the full event catalog