---
title: "Handle interactive replies"
description: "Learn how to parse dynamic quick-replies and list selections sent by WhatsApp users."
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.wazapin.id/llms.txt
> Use this file to discover all available pages before exploring further.

# Handle interactive replies

When a user interacts with a [buttons message](/getting-started/send-buttons) or a [list menu](/getting-started/send-list) sent by your workspace, Wazapin triggers a webhook event. 

Follow this guide to identify interactive replies and extract the exact choice the customer made.

## When this matters

Interactive replies are critical for:
- Routing customers to different support flows (e.g. Sales vs Support).
- Confirming transaction actions (e.g., tapping "Confirm Order" or "Reschedule").
- Implementing automated chatbot menus and FAQ navigators.

---

## Processing flow

Handling an interactive choice involves three steps:

```mermaid
graph TD
A[User taps button or list item] --> B[Webhook message.new msg_type: interactive]
B --> C[GET /v1/messages/{id} to load message details]
C --> D{interactive.type?}
D -->|button_reply| E[Read button ID & title]
D -->|list_reply| F[Read row ID, title & description]
```

### 1. Receive interactive webhook
Wazapin POSTs a lightweight `message.new` webhook containing `msg_type: "interactive"`.

Here is an example webhook payload:

```json Interactive webhook payload
{
  "message_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "conversation_id": "0f89b0f9-74b4-44f9-b9b6-48f6d4de57aa",
  "contact_id": "c1a2b3c4-d5e6-7890-abcd-ef1234567890",
  "channel_id": "wzp_abc123",
  "direction": "inbound",
  "from_phone": "6281234567890",
  "msg_type": "interactive"
}
```

### 2. Fetch selection details
Call **GET /v1/messages/\{messageID\}** using the `message_id` from the payload. The API response will contain an `interactive` object within the `content` block detailing the user's input.

---

## Interactive structures

Depending on the message type, the returned details will have one of two shapes:

### Quick-reply buttons (`button_reply`)
If the user tapped a button, the `interactive` block will have a `type` of `"button_reply"`:

```json
{
  "data": {
"id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"type": "interactive",
"content": {
  "interactive": {
    "type": "button_reply",
    "button_reply": {
      "id": "confirm_yes",
      "title": "Yes, I confirm"
    }
  }
}
  }
}
```

### List menu row (`list_reply`)
If the user picked an option from a list menu, the `interactive` block will have a `type` of `"list_reply"`:

```json
{
  "data": {
"id": "b2c3d4e5-f6a7-8901-bcde-f12345678901",
"type": "interactive",
"content": {
  "interactive": {
    "type": "list_reply",
    "list_reply": {
      "id": "support_agent",
      "title": "Talk to Support",
      "description": "Connect to a live agent"
    }
  }
}
  }
}
```

---

## Code example

Here is how to handle the webhook and process the user's choice:

### cURL

```bash
# Fetch the message details to check what choice the user made
curl -X GET "https://api.wazapin.com/v1/messages/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \
  -H "X-Api-Key: YOUR_API_KEY" \
  -H "Accept: application/json"
```
### TypeScript

```typescript
import express from "express";
import { Webhook } from "svix";
import { WazapinClient } from "@wazapin/sdk";

const app = express();
const wazapin = new WazapinClient({ apiKey: process.env.WAZAPIN_API_KEY });
const wh = new Webhook(process.env.WAZAPIN_WEBHOOK_SECRET!);

app.post("/webhooks/wazapin", express.raw({ type: "application/json" }), async (req, res) => {
  try {
wh.verify(req.body, req.headers as Record<string, string>);
  } catch (err) {
return res.status(403).send("Invalid signature");
  }

  const payload = JSON.parse(req.body.toString("utf8"));

  if (payload.direction === "inbound" && payload.msg_type === "interactive") {
const messageId = payload.message_id;

// 1. Retrieve the interactive choice
const { data: message } = await wazapin.messages.get(messageId);
const interactive = message.content?.interactive;

if (interactive) {
  if (interactive.type === "button_reply") {
    const button = interactive.button_reply;
    console.log(`User tapped button ID: ${button.id} with label: ${button.title}`);
    // Route customer to corresponding flow
  } else if (interactive.type === "list_reply") {
    const row = interactive.list_reply;
    console.log(`User selected list option ID: ${row.id} with label: ${row.title}`);
    // Handle list selection
  }
}
  }

  res.status(200).send("OK");
});
```
### Python

```python
from fastapi import FastAPI, Request, HTTPException
from svix.webhooks import Webhook, WebhookVerificationError
import requests
import os

app = FastAPI()
wh = Webhook(os.environ["WAZAPIN_WEBHOOK_SECRET"])
API_KEY = os.environ["WAZAPIN_API_KEY"]

@app.post("/webhooks/wazapin")
async def handle_webhook(request: Request):
body = await request.body()
try:
    wh.verify(body, dict(request.headers))
except WebhookVerificationError:
    raise HTTPException(status_code=403, detail="Invalid signature")

payload = await request.json()

if payload.get("direction") == "inbound" and payload.get("msg_type") == "interactive":
    message_id = payload.get("message_id")

    # 1. Retrieve message details
    response = requests.get(
        f"https://api.wazapin.com/v1/messages/{message_id}",
        headers={"X-Api-Key": API_KEY, "Accept": "application/json"}
    )

    if response.status_code == 200:
        message_data = response.json().get("data", {})
        interactive = message_data.get("content", {}).get("interactive", {})
        interactive_type = interactive.get("type")

        if interactive_type == "button_reply":
            button = interactive.get("button_reply", {})
            print(f"User tapped button: {button.get('id')} ({button.get('title')})")
        elif interactive_type == "list_reply":
            row = interactive.get("list_reply", {})
            print(f"User selected option: {row.get('id')} ({row.get('title')})")

return {"ok": True}
```

---

## Troubleshooting

### Webhook arrives but `interactive` is empty
Ensure you query the message by ID via `GET /v1/messages/{messageID}`. Webhook bodies are intentionally slimmed down and do not include the interactive reply block inline.

### User pressed multiple buttons
WhatsApp does not allow multiple choice selections on a single message. Tap events are atomic and arrive as individual webhooks. If a user tries to tap buttons on older messages, your system will receive a new webhook event with a unique `message_id`. You can track the original message ID via the conversation timeline or message parent headers if applicable.

---

## Related links
- [Send buttons](/getting-started/send-buttons)
- [Send list options](/getting-started/send-list)
- [Webhooks overview](/receive-messages/overview)

Source: https://docs.wazapin.id/guides/handle-interactive-replies/index.mdx
