# List reaction events for a WhatsApp message

`GET /v1/whatsapp/messages/{message_id}/reaction-events`

Returns the changes made to this message's reactions as a cursor-paginated
list, newest first: each emoji placed, each one replaced by a different
emoji, and each one taken back. Entries are never edited, so a contact who
reacts, changes their mind and then removes it leaves three of them.

One case is missing rather than recorded. A reaction is matched to the
message it was placed on through a provider id we keep for 15 days, while
WhatsApp accepts a reaction on a message up to 30 days old, so one placed on
a message older than that cannot be matched and is recorded nowhere: not
here, and not in the message's `reactions`.

Use this to show who reacted and when, or to find out what became of a
reaction that never appeared: a `failed` or `rejected` entry carries the
reason on `error`. For what currently stands on the message, read its
`reactions`, which folds this log down to one entry per sender.

Pass the response's `next_cursor` back as `starting_after` to fetch the next
page.

Reaction events are kept for **30 days**, counted from when the message they
belong to was accepted rather than from the reaction itself. They therefore
go at about the same time as the message, not 30 days after the last
reaction on it.

## Code samples

### TypeScript

```ts
for await (const event of bird.whatsapp.reaction.listEvents("wam_01krdgeqcxet5s7t44vh8rt9mg")) {
  console.log(event.id, event.emoji, event.status);
}
```

### Python

```py
for event in client.whatsapp.reaction.list_events("wam_01krdgeqcxet5s7t44vh8rt9mg"):
    print(event.id, event.emoji, event.status)
```

### Go

```go
for event, err := range client.Whatsapp.Reaction.ListEvents(context.Background(), "wam_01krdgeqcxet5s7t44vh8rt9mg", bird.WhatsappReactionListEventsParams{}) {
	if err != nil {
		log.Fatal(err)
	}
	fmt.Println(event.Id, event.Emoji, event.Status)
}
```

### PHP

```php
foreach ($bird->whatsapp->reaction->listEvents('wam_01krdgeqcxet5s7t44vh8rt9mg') as $event) {
    echo $event->getId(), ' ', $event->getEmoji(), ' ', $event->getStatus(), "\n";
}
```

### CLI

```sh
bird whatsapp reaction list-events <message-id>
```

### cURL

```sh
curl -X GET "https://us1.platform.bird.com/v1/whatsapp/messages/{message_id}/reaction-events" \
  -H "Authorization: Bearer $TOKEN" \
  --url-query "limit=25"
```

## Example response `200`

```json
{
  "data": [
    {
      "id": "war_01krdgeqcxet5s7t44vh8rt9mg",
      "emoji": "👍",
      "status": "received",
      "from": {
        "phone_number": "+15550001111",
        "bsuid": "NL.xxxx"
      },
      "error": {
        "code": "insufficient_balance",
        "description": "Message could not be delivered.",
        "meta_error_code": "131026"
      },
      "occurred_at": "2026-08-28T19:01:10Z"
    }
  ],
  "next_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE0OjAzOjEwWlwiIiwiaSI6IjAxOTJmM2IxLTRjN2UtN2EyYi05ZDYxLThmM2E1YzJlN2I0MCJ9",
  "prev_cursor": null,
  "refresh_cursor": "eyJ2IjoxLCJzIjoiXCIyMDI2LTA1LTI1VDE2OjQyOjAxWlwiIiwiaSI6IjAxOTJmM2IxLTllMDQtN2NkMy1iODE3LTJhNmY0ZDFjOGUwOSJ9"
}
```

## Path parameters

- `message_id` (string): ID of the message whose reactions to read, as returned in the `id` field of the message.

## Query parameters

- `limit` (integer): Maximum number of items to return per page.
- `starting_after` (string): Cursor from the `next_cursor` field of a previous list response. Returns items immediately after the cursor position in the current sort order.
- `ending_before` (string): Cursor from the `prev_cursor` or `refresh_cursor` field of a previous list response. Returns items immediately before the cursor position in the current sort order. `prev_cursor` returns the preceding page. `refresh_cursor` anchors at the first row of that response, which on a newest-first sort is how to fetch the items that have appeared since.

## Response body

- `data` (array of object, required): Changes to this message's reactions, newest first.
- `data.id` (string, required): ID of this entry, unique within the message's reaction log.
- `data.emoji` (nullable string, required): The emoji this entry placed, as WhatsApp sent it and not normalized. Null when the entry took a reaction back rather than placing one. Always present, so null is the removal itself rather than a value we are missing.
- `data.status` (string, required)
- `data.from` (object, required): Who made the change. Your business number on a reaction you placed, the contact on one they placed.
- `data.from.phone_number` (string): Phone number in E.164 format, when known.
- `data.from.bsuid` (string): Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.
- `data.from.username` (string): Present only on a message received from a WhatsApp user, on `from`; never on an outbound send's `to`, where the profile is not known. Absent when the contact has not adopted one, and on a message received before this workspace started recording them. Same form as a number's own username (`WhatsAppNumberProfile.username`), without a leading `@`; a message cannot be addressed by it.
- `data.from.display_name` (string): Present only on a message received from a WhatsApp user, on `from`; never on an outbound send's `to`, where the profile is not known. Absent when the message carries no profile, and on a message received before this workspace started recording them.
- `data.error` (nullable object): Why the change did not take effect. Always carried by a `failed` or `rejected` entry, and never by any other, so a failure always says what went wrong. Absent rather than null on the entries that did take effect. The schema leaves it optional because that is a conditional the generators do not express.
- `data.error.code` (string, required)

  Standardized failure reason:

  - `insufficient_balance`: The workspace wallet could not fund the send.
  - `price_not_found`: No price was configured for the destination and template.
  - `internal_error`: An unexpected service failure occurred.
  - `undeliverable`: The recipient could not be reached.
  - `service_window_expired`: The 24-hour service window closed; send a template.
  - `rate_limited`: The send was throttled.
  - `recipient_suppressed`: The recipient is on the workspace suppression list.
  - `media_rejected`: WhatsApp could not fetch the media URL, or refused the file it found there; `description` carries its reason.

  This is an open enum. Accept unrecognized values.

  Possible values (may grow over time): `insufficient_balance`, `price_not_found`, `internal_error`, `undeliverable`, `service_window_expired`, `rate_limited`, `recipient_suppressed`, `media_rejected`
- `data.error.description` (string, required): Human-readable explanation of the failure.
- `data.error.meta_error_code` (nullable string): Raw error code from the WhatsApp Cloud API, when available, for low-level debugging.
- `data.error.occurred_at` (string, required): When the failure occurred.
- `data.occurred_at` (string, required): When the change was made.
- `next_cursor` (nullable string, required): Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.
- `prev_cursor` (nullable string, required): Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.
- `refresh_cursor` (nullable string, required): Refresh anchor, the first row of this response. Pass back as `ending_before` to fetch what precedes it in the current sort order. On a newest-first sort those are the items that have appeared since; on any other sort they are the items that sort earlier, so refreshing such a list means re-fetching it instead. Non-`null` whenever `data` is non-empty; `null` only on an empty page. Distinct from `prev_cursor`.

## Related resources

- [Should I use a Bird SDK or call the API directly?](/explained/platform/should-i-use-an-sdk-or-call-the-api-directly) (answer)
- [Build your first integration](/learn/paths/integration) (course)
- [Send your first email](/docs/get-started/send-your-first-email) (docs)

[Get an implementation brief](/learn/workspace?topic=api-basics)
