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:

FieldNotes
action.buttons1–3 buttons. More than 3 is refused by WhatsApp (502)
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 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…" } }
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",
  "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.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 (also copied into body); description is the list row's description, or null.
  • A tap on a template's quick-reply button arrives differently: type is "button" and content.button holds { "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.