# Receiving WhatsApp interactive replies

A tap on a reply button, a list row, or a template's quick-reply button arrives as its own inbound message carrying `interactive_reply`. The arm gives you back the handle you set on the send, so a flow branches on your own identifier rather than on the label the contact saw.

## What an inbound interactive reply carries

`type` names the kind of tap, and the field of the same name carries it:

```json
{
  "id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
  "direction": "inbound",
  "from": { "phone_number": "+14155550100" },
  "to": { "phone_number": "+13124495569" },
  "status": "received",
  "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
  "interactive_reply": {
    "type": "button",
    "button": { "slug": "cancel-booking", "text": "Cancel" }
  },
  "created_at": "2026-08-25T09:04:11Z"
}
```

| `type`   | Field    | What it carries                                                                            |
| -------- | -------- | ------------------------------------------------------------------------------------------ |
| `button` | `button` | The `slug` and `text` of a reply button, or of a template's quick-reply button             |
| `list`   | `list`   | The `slug` and `text` of the row the contact chose, plus its `description` when it had one |

A list row replaces `button` with `list` and adds the second line the row showed:

```json
{
  "interactive_reply": {
    "type": "list",
    "list": {
      "slug": "priority_express",
      "text": "Priority Mail Express",
      "description": "Next day to 2 days"
    }
  }
}
```

`slug` is the handle you declared and the contact never saw; `text` is the label they read. Branch on `slug`. Labels get reworded and translated, and on a tap on a template's quick-reply button the `slug` is the payload that template declared, which WhatsApp sets to the button's own label.

The kind list is open: WhatsApp adds interactive kinds over time, so treat a `type` you do not recognize as a future kind rather than an error, and fall back to logging the message instead of failing the read.

## Tying a tap to what you sent

`in_reply_to_message_id` names the message that carried the button or the menu, which is how you know which question this answer belongs to. WhatsApp does not report it on every tap, and resolution can also miss, in which case the field is omitted rather than reported empty. The hub's [quoted replies](/docs/guides/whatsapp/receiving-whatsapp#quoted-replies) covers what a miss means and how long a quoted message stays resolvable.

Where correlation has to be reliable, put your own reference in the `slug` itself, or in `metadata` on the send, rather than depending on the field. See [quoting a message](/docs/guides/whatsapp/sending-whatsapp#quoting-a-message) for the send side.

## The two taps that arrive elsewhere

Two interactive types answer without an `interactive_reply` at all:

- A [location request](/docs/guides/whatsapp/message-types/interactive/location-requests) comes back as an ordinary inbound [location](/docs/guides/whatsapp/receiving-whatsapp/location).
- A [contact info request](/docs/guides/whatsapp/message-types/interactive/contact-info-requests) comes back as [contact cards](/docs/guides/whatsapp/receiving-whatsapp/contact-cards), with `origin` set to `contact_request`.

A [link button](/docs/guides/whatsapp/message-types/interactive/cta-url-buttons) sends nothing back: the contact leaves for the URL, and no inbound message records the tap. An integration watching only `interactive_reply` misses all three.

## The webhook payload

`whatsapp.received` carries the `interactive_reply` arm on the event envelope, so a bot can answer a tap without reading the message back:

```json
{
  "type": "whatsapp.received",
  "timestamp": "2026-08-25T09:04:11.118Z",
  "data": {
    "whatsapp_id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
    "workspace_id": "ws_01ky7m235keycbnwyajabe1a6b",
    "direction": "inbound",
    "from": { "phone_number": "+14155550100", "display_name": "Alex Rivera" },
    "to": { "phone_number": "+13124495569" },
    "in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
    "interactive_reply": {
      "type": "button",
      "button": { "slug": "cancel-booking", "text": "Cancel" }
    },
    "tags": null,
    "metadata": null
  }
}
```

## Things to watch

- **`interactive` and `interactive_reply` are opposite directions.** `interactive` is what you sent and never arrives inbound; `interactive_reply` is what the contact tapped and never appears on an outbound message.
- **A tap resets the service window.** It is an inbound message, so it reopens 24 hours of free-form replies the same way a text does.
- **A contact can tap the same button twice.** Nothing deduplicates taps, so each one is its own message with its own ID. Make the action you take on a `slug` idempotent.
- **A tap on an old menu still arrives.** A contact scrolling back can tap a button from days ago, so validate that the flow is still open rather than assuming the tap answers your latest message.

## Next steps

- [How receiving works](/docs/guides/whatsapp/receiving-whatsapp): the inbound envelope, media fetching, and the `whatsapp.received` webhook
- [WhatsApp interactive messages](/docs/guides/whatsapp/message-types/interactive): the six types a recipient can tap
- [WhatsApp reply buttons](/docs/guides/whatsapp/message-types/interactive/reply-buttons): the send side of a button tap
- [WhatsApp list menus](/docs/guides/whatsapp/message-types/interactive/list-menus): the send side of a row choice