What Interactive Service Messages Are
Interactive service messages are non-template messages that include UI elements — buttons, lists, carousels, location-request prompts — beyond plain text or media. They are sent inside an open customer service window using the standard /messages endpoint with type: "interactive".
User responses (button taps, list selections, location shares) come back as inbound messages webhooks with the relevant action payload. In Dualhook setups, these webhooks go directly to your endpoint via Webhook Override — Dualhook does not ingest them.
The Interactive Wrapper
All interactive types share the same outer structure:
{
"messaging_product": "whatsapp",
"recipient_type": "individual",
"to": "12015550123",
"type": "interactive",
"interactive": {
"type": "<INTERACTIVE_TYPE>",
"header": { /* optional, type-specific shape */ },
"body": { "text": "<BODY_TEXT>" },
"footer": { "text": "<FOOTER_TEXT>" },
"action": { /* required, type-specific shape */ }
}
}
interactive.type selects which sub-type you're sending. The header, body, footer, and action fields each have type-specific shapes. Body is required for every type; header and footer are usually optional.
Type Overview
interactive.type | What it does | Header? | Footer? |
|---|---|---|---|
cta_url | Map a URL to a button | Optional (text/image/video/document) | Optional |
button | Up to 3 reply buttons | Optional (text/image/video/document) | Optional |
list | Modal list with up to 10 sections × 10 rows | Optional (text only) | Optional |
carousel | Horizontal scroll of 2–10 media cards | Not supported | Not supported |
location_request_message | Send-location button | Not supported | Not supported |
address_message | India-only address form (see Address Messages) | — | — |
flow | WhatsApp Flows (see Meta's Flows docs) | — | — |
CTA URL Button (cta_url)
Map a URL to a labeled button instead of dumping a raw URL into message text. Useful when the URL is long or obfuscated by tracking parameters.
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "interactive",
"interactive": {
"type": "cta_url",
"header": {
"type": "image",
"image": { "link": "https://example.com/banner.png" }
},
"body": { "text": "Tap the button below to see available dates." },
"footer": { "text": "Dates subject to change." },
"action": {
"name": "cta_url",
"parameters": {
"display_text": "See Dates",
"url": "https://example.com?clickID=abc123"
}
}
}
}
| Field | Limit | Notes |
|---|---|---|
body.text | 1024 chars | Required. URLs auto-hyperlinked. |
header.type | — | text, image, video, or document. Optional. |
footer.text | 60 chars | Optional. URLs auto-hyperlinked. |
action.parameters.display_text | 20 chars | Required. Button label. |
action.parameters.url | — | Required. Loaded in default browser on tap. |
Reply Buttons (button)
Up to 3 predefined reply buttons. Each tap triggers an inbound messages webhook with the button's id so your backend can route the user's choice.
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "interactive",
"interactive": {
"type": "button",
"header": { "type": "text", "text": "Choose an option" },
"body": { "text": "How can we help today?" },
"footer": { "text": "Reply with one option." },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "track_order", "title": "Track order" } },
{ "type": "reply", "reply": { "id": "talk_to_agent","title": "Talk to agent" } },
{ "type": "reply", "reply": { "id": "stop_messages","title": "Stop messages" } }
]
}
}
}
| Field | Limit |
|---|---|
action.buttons | Max 3 |
reply.id | 256 chars |
reply.title | 20 chars |
List (list)
A list message displays a single button in the chat that, when tapped, opens a modal with up to 10 sections, each containing up to 10 rows. Total row count across all sections is capped at 10.
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "interactive",
"interactive": {
"type": "list",
"header": { "type": "text", "text": "Choose a category" },
"body": { "text": "Pick one and we'll send relevant updates." },
"footer": { "text": "You can change preferences anytime." },
"action": {
"button": "Choose category",
"sections": [
{
"title": "Notifications",
"rows": [
{ "id": "orders", "title": "Order updates", "description": "Shipping, delivery, returns" },
{ "id": "billing", "title": "Billing alerts", "description": "Invoices and renewals" }
]
},
{
"title": "Promotions",
"rows": [
{ "id": "deals", "title": "Deals", "description": "Weekly offers" }
]
}
]
}
}
}
| Field | Limit |
|---|---|
action.button | 20 chars (label on the modal-trigger button) |
sections | Max 10 |
rows (per section) | Max 10 |
Total rows across all sections | Max 10 |
row.title | 24 chars |
row.description | 72 chars |
Media Carousel (carousel)
Horizontally scrollable cards (2–10), each with an image or video header, optional body text, and either one URL button or one or more quick-reply buttons. Button shape and count must be identical across all cards.
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "interactive",
"interactive": {
"type": "carousel",
"body": { "text": "Here are three of our latest arrivals:" },
"action": {
"cards": [
{
"card_index": 0,
"type": "cta_url",
"header": { "type": "image", "image": { "link": "https://example.com/blue.jpg" } },
"body": { "text": "*Blue Echeveria*\n\nA rosette-shaped succulent." },
"action": {
"name": "cta_url",
"parameters": { "display_text": "Buy now", "url": "https://shop.example.com/blue" }
}
},
{
"card_index": 1,
"type": "cta_url",
"header": { "type": "image", "image": { "link": "https://example.com/zebra.jpg" } },
"body": { "text": "*Zebra Haworthia*\n\nWhite stripes on green leaves." },
"action": {
"name": "cta_url",
"parameters": { "display_text": "Buy now", "url": "https://shop.example.com/zebra" }
}
}
]
}
}
}
For quick-reply variants, use action.buttons (array of quick_reply objects with id + title) on each card instead of action.parameters.
| Field | Limit |
|---|---|
interactive.body.text | 1024 chars (required) |
cards | Min 2, max 10 |
card.body.text | 160 chars, up to 2 line breaks (optional) |
card.header.type | image or video only |
URL button display_text | 20 chars |
Quick-reply title | 20 chars |
Quick-reply id | 256 chars |
Headers, footers, and other interactive components are not supported on the carousel wrapper itself — only on individual cards. All cards must use the same button shape.
Location Request (location_request_message)
Display body text plus a "send location" button. When the user taps it, WhatsApp opens the location-sharing screen.
{
"messaging_product": "whatsapp",
"to": "12015550123",
"type": "interactive",
"interactive": {
"type": "location_request_message",
"body": {
"text": "Let's start with your pickup. *Enter an address* or *share your current location*."
},
"action": { "name": "send_location" }
}
}
When the user shares their location, you receive a messages webhook with type: "location":
{
"messages": [
{
"context": {
"from": "15550001234",
"id": "wamid.HBgL..."
},
"from": "12015550123",
"id": "wamid.HBgL...",
"timestamp": "1702920965",
"location": {
"latitude": 40.782910059774,
"longitude": -73.959075808525,
"name": "Solomon R. Guggenheim Museum",
"address": "1071 5th Ave, New York, NY 10128"
},
"type": "location"
}
]
}
name and address only appear if the user chose to share them. latitude/longitude are always present when the user shares.
Responses and Webhooks
The /messages API response shape is the same as for any service message: a messages array with the WhatsApp message ID. Acceptance does not confirm delivery — see Messaging Overview → Delivery Status Lifecycle.
User actions on interactive messages (button taps, list selections, location shares) arrive as inbound messages webhooks. Common shapes:
| User action | Inbound webhook type | Key fields |
|---|---|---|
Tap a button reply | interactive.button_reply | interactive.button_reply.id, interactive.button_reply.title |
Pick a list row | interactive.list_reply | interactive.list_reply.id, interactive.list_reply.title, interactive.list_reply.description |
| Tap a carousel quick-reply | interactive.button_reply | as above (per card) |
| Tap a CTA URL button | No inbound webhook for the tap itself (the URL opens externally) | — |
| Share location | location | latitude, longitude, optional name/address |
For Dualhook customers these webhooks bypass our app entirely — they go straight to your endpoint via Webhook Override. See Messaging Webhook for full envelope and signature details.