---
title: "List reaction events for a WhatsApp message"
method: GET
path: "/v1/whatsapp/messages/{message_id}/reaction-events"
tags: ["whatsapp-messages"]
---

# List reaction events for a WhatsApp message

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

## Request

- Base URL: `https://us1.platform.bird.com`
- URL: `https://us1.platform.bird.com/v1/whatsapp/messages/{message_id}/reaction-events`
- Auth: one of:
  - HTTP bearer
  - API key in cookie `bird_session`

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.

## Path parameters

- `message_id` string, required

## Query parameters

- `limit` integer
- `starting_after` string
- `ending_before` string

## Headers

- `X-Workspace-Id` string
- `X-Organization-Id` string

## Response `200`

Paginated list of reaction changes for this WhatsApp message.

- WhatsAppReactionEventList
  - `data` WhatsAppReactionEvent[], required: Changes to this message's reactions, newest first.
    - `id` string, required
    - `emoji` string, nullable, 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.
    - `status` 'received' | 'sent' | 'failed' | 'rejected', required: What became of one reaction change: - `received` means the contact placed or removed the reaction and WhatsApp told us about it. Every inbound entry carries this. - `sent` means your reaction reached WhatsApp. Reactions have no delivery or read receipt, so this is as far as an outbound entry gets. - `failed` means WhatsApp refused it. `error` says why, most often because the contact deleted the message. The grounds we can check for ourselves (a message you sent, one that is itself a reaction, one over 30 days old) are refused when you ask, so they do not reach here. - `rejected` means we refused it before it reached WhatsApp, so nothing was sent. `error` says why. Only `received` and `sent` entries change what stands on the message, so those are the ones the message's `reactions` are folded from.
    - `from` WhatsAppAddress, required: Sender or recipient of a WhatsApp message: a phone number, a business-scoped user ID, or both. A message received from a WhatsApp user carries whatever profile they publish, which may be neither.
      - `phone_number` string: Phone number in E.164 format, when known.
      - `bsuid` string: Business-scoped user ID, Meta's identifier for the WhatsApp user. Present only on the WhatsApp-user side of the message.
      - `group_id` string
      - `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.
      - `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.
    - `error` WhatsAppError, nullable: Failure detail for a message that could not be delivered or was rejected.
      - `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.
      - `description` string, required: Human-readable explanation of the failure.
      - `meta_error_code` string, nullable: Raw error code from the WhatsApp Cloud API, when available, for low-level debugging.
      - `occurred_at` string, date-time, required: When the failure occurred.
    - `occurred_at` string, date-time, required: When the change was made.
  - `next_cursor` string, nullable, required: Cursor for the next page. Pass back as `starting_after` to advance forward. `null` when no next page exists.
  - `prev_cursor` string, nullable, required: Cursor for the previous page. Pass back as `ending_before` to step backward. `null` when no previous page exists.
  - `refresh_cursor` string, nullable, 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`.

## Other responses

- `400`: Bad request
- `401`: Authentication required
- `403`: Insufficient permissions
- `404`: Resource not found
- `422`: The request has invalid field values, violates a business rule, or carries a query parameter the endpoint does not declare. Field validation errors use `type: validation_error` and include the affected fields in `details`. Business-rule errors identify the failed rule in `type`.
- `429`: Rate limit exceeded
- `500`: Internal server error
- `503`: The service is temporarily unavailable. If `Retry-After` is present, wait for that delay before retrying; otherwise, retry with exponential backoff. Reuse the same idempotency key and request when retrying a mutation.

---

[API](https://skmtc.dev/messagebird/apis/bird-api.md) · [All operations](https://skmtc.dev/messagebird/apis/bird-api/llms.txt) · [OpenAPI document](https://skmtc.dev/messagebird/apis/bird-api/revisions/3e2a50466454?raw)
