Interactive messages
Interactive messages put tappable UI — reply buttons, scrollable lists, or a call-to-action link button — into a WhatsApp thread. You send them through the same POST /send endpoint using the interactive content field, and the customer's tap comes back to you on the received webhook.
All three shapes below are Meta Cloud API interactive objects passed through verbatim — Telenow does not reshape them. interactive.type must be one of button, list, or cta_url; other Meta interactive types (flow, product, product_list, catalog_message, address_message, location_request_message) are refused with 502 interactive type must be button, list or cta_url. That type is the only thing Telenow checks: the field limits in the tables below are WhatsApp's, and a message that breaks one is refused by WhatsApp and comes back as 502 with WhatsApp's error text (see Errors).
Interactive messages need a Meta Cloud number (native or your own Meta app); any other number returns 502 (Photos, files, buttons, locations and reactions need a WhatsApp Cloud API number…). They are free-form content, so they only send inside the 24-hour customer service window; outside it, start with a template.
Reply buttons
A button interactive shows up to three tappable quick-reply buttons under a body text. Each button has your own id (echoed back on the tap) and a title (what the customer sees, ≤20 chars).
curl -X POST https://api.telenow.ai/api/app-whatsapp/send \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"channelId": "b1e2c3d4-…",
"to": "+919876543210",
"interactive": {
"type": "button",
"body": { "text": "Did this resolve your issue?" },
"action": {
"buttons": [
{ "type": "reply", "reply": { "id": "resolved_yes", "title": "Yes, thanks" } },
{ "type": "reply", "reply": { "id": "resolved_no", "title": "Still stuck" } },
{ "type": "reply", "reply": { "id": "talk_human", "title": "Talk to an agent" } }
]
}
}
}'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }
Rules that mirror Meta's:
| Field | Notes |
|---|---|
action.buttons | 1–3 buttons. More than 3 is refused by WhatsApp (502) |
reply.id | Your identifier, ≤256 chars. Comes back verbatim on the tap — make it stable and meaningful |
reply.title | ≤20 chars, must be unique within the message |
header | Optional — { "type": "text", "text": "…" }. An image / video / document header must reference a public https link, e.g. { "type": "image", "image": { "link": "https://…" } } — not a mediaId |
footer | Optional — { "text": "…" }, ≤60 chars |
Staged mediaIds from POST /channels/{id}/media are only translated for plain media sends and template headers — never inside interactive, where the object is forwarded to Meta verbatim, so a media header must carry a public https link.
List messages
A list interactive opens a menu the customer scrolls through. It has one action.button label (the text that opens the sheet) and one or more sections holding up to 10 rows in total. Every row has an id, a title, and an optional description.
curl -X POST https://api.telenow.ai/api/app-whatsapp/send \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"channelId": "b1e2c3d4-…",
"to": "+919876543210",
"interactive": {
"type": "list",
"header": { "type": "text", "text": "Book an appointment" },
"body": { "text": "Pick a slot that works for you." },
"footer": { "text": "Times shown in IST" },
"action": {
"button": "View slots",
"sections": [
{
"title": "Today",
"rows": [
{ "id": "slot_1500", "title": "3:00 PM", "description": "Dr. Rao — 30 min" },
{ "id": "slot_1630", "title": "4:30 PM", "description": "Dr. Rao — 30 min" }
]
},
{
"title": "Tomorrow",
"rows": [
{ "id": "slot_1000", "title": "10:00 AM", "description": "Dr. Mehta — 30 min" }
]
}
]
}
}
}'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }
| Field | Notes |
|---|---|
action.button | ≤20 chars. The label that opens the list |
sections | ≥1 section; ≤10 rows total across all sections |
sections[].title | Optional section heading, ≤24 chars |
rows[].id | Your identifier, ≤200 chars. Echoed back on selection |
rows[].title | ≤24 chars, unique across the whole list |
rows[].description | Optional, ≤72 chars |
Call-to-action URL
A cta_url interactive renders a body plus a single button that opens a URL — cleaner than pasting a raw link, and it survives Meta's link-formatting. This one has no tap webhook: the customer just leaves for your URL, so there is nothing to receive back.
curl -X POST https://api.telenow.ai/api/app-whatsapp/send \
-H "Authorization: Bearer vai_app_…" \
-H "Content-Type: application/json" \
-d '{
"channelId": "b1e2c3d4-…",
"to": "+919876543210",
"interactive": {
"type": "cta_url",
"body": { "text": "Your order #1234 is ready. Track it live below." },
"action": {
"name": "cta_url",
"parameters": {
"display_text": "Track order",
"url": "https://shop.example.com/track/1234"
}
}
}
}'
{ "success": true, "data": { "wamid": "wamid.HBgLMTQxNTU1MDEwMBUCABIYFj…" } }
action.name must be the literal string "cta_url". parameters.display_text is the button label; parameters.url must be a fully-qualified https:// link. An optional header / footer can wrap the body just like the other types — a media header must reference a public https link (e.g. { "type": "image", "image": { "link": "https://…" } }), not a staged mediaId.
Handling the tap
When the customer taps a reply button or picks a list row, Telenow delivers a whatsapp.message.received webhook whose content.reply carries the choice — see Webhooks & events for the full payload and signature verification.
{
"event": "whatsapp.message.received",
"channelId": "b1e2c3d4-…",
"from": "+919876543210",
"wamid": "wamid.HBgLM…",
"type": "interactive",
"body": "Still stuck",
"content": {
"reply": { "kind": "button_reply", "id": "resolved_no", "title": "Still stuck", "description": null }
},
"timestamp": "2026-07-18T09:31:00+00:00"
}
content.reply.kindis"button_reply"for a reply-button tap and"list_reply"for a list-row selection.content.reply.idis the exactidyou set on the button or row — key your routing off this, not the human-readabletitle.content.reply.titleis the label the customer saw (also copied intobody);descriptionis the list row's description, ornull.- A tap on a template's quick-reply button arrives differently:
typeis"button"andcontent.buttonholds{ "payload", "text" }.
Because the id round-trips unchanged, you can encode routing hints into it (e.g. slot_1500, talk_human) and branch on it directly when the webhook arrives.
With your org API key
With your org API key (X-API-Key: vai_live_…), send the same interactive object to the org route — the channel goes in the path, so there's no channelId in the body:
curl -X POST https://api.telenow.ai/api/orgs/{orgId}/whatsapp-hub/channels/{channelId}/send \
-H "X-API-Key: vai_live_…" \
-H "Content-Type: application/json" \
-d '{
"to": "+919876543210",
"interactive": {
"type": "button",
"body": { "text": "Did this resolve your issue?" },
"action": { "buttons": [ { "type": "reply", "reply": { "id": "resolved_yes", "title": "Yes, thanks" } } ] }
}
}'
To answer inside a conversation, post the same body without to to …/whatsapp-hub/threads/{threadId}/send. The response, the rules and the Meta Cloud requirement are the same as above; the key needs the owner, admin or developer role.
Related
- Webhooks & events — receive and verify the tap (
content.reply). - Sending template messages — start conversations outside the 24-hour window.
- Location, contacts & reactions — the other rich
/sendcontent fields. - Media messages — images, documents, audio and video.
- WhatsApp overview — connect a number and find your
channelId.