Interactive messages

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 rejected with 400 interactive type must be button, list or cta_url. Interactive messages 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:

FieldNotes
action.buttons1–3 buttons. More than 3 → 400
reply.idYour identifier, ≤256 chars. Comes back verbatim on the tap — make it stable and meaningful
reply.title≤20 chars, must be unique within the message
headerOptional — { "type": "text", "text": "…" }. An image / video / document header must reference a public https link, e.g. { "type": "image", "image": { "link": "https://…" } } — not a mediaId
footerOptional — { "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, each holding up to 10 rows. 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…" } }
FieldNotes
action.button≤20 chars. The label that opens the list
sections≥1 section; ≤10 rows total across all sections
sections[].titleOptional section heading, ≤24 chars
rows[].idYour identifier, ≤200 chars. Echoed back on selection
rows[].title≤24 chars, unique across the whole list
rows[].descriptionOptional, ≤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",
  "content": {
    "reply": { "kind": "button_reply", "id": "resolved_no", "title": "Still stuck" }
  },
  "timestamp": "2026-07-18T09:31:00Z"
}
  • content.reply.kind is "button_reply" for a reply-button tap and "list_reply" for a list-row selection.
  • content.reply.id is the exact id you set on the button or row — key your routing off this, not the human-readable title.
  • content.reply.title is the label the customer saw.

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.