Skip to main content
Yes — you can send buttons and lists through the API without a template. Interactive buttons and single-select lists are free-form (service) messages on the same endpoint you already use:
No template to create, no Meta review. On Meta Cloud API (official) numbers there is one condition: the 24-hour service window must be open — which it always is when your bot is answering a message the customer just sent. On unofficial numbers there is no window. This is the shape of a typical bot: the customer writes, you answer with a menu, the customer picks an option, you answer the next step.

Pick the right interactive type

All of them need text — the message body above the buttons or the list button. They are mutually exclusive: one interactive type per message. (buttons can also ride on a template send; list, carousel and flow cannot.)

Send a list

The response is the usual 202 with id and correlationId. Keep the id: it is how you tie the customer’s choice back to this menu (see Match the choice below). header and buttons are not allowed together with list.
More than 10 options? Send the first 9 and use the 10th row as “More options” (id: "page-2"). When the customer picks it, send the next list. Ten is Meta’s cap and it is enforced on every number type.

Send reply buttons

Up to 3 buttons. Besides reply, buttons can be url, call or copy — the full shapes are in Send interactive.
On Meta (official) numbers, do not mix a url button with reply buttons in a free-form send — one ordering is rejected, the other is accepted and never delivered. Send the link in its own message. Details in Send interactive.

Receive the customer’s choice

The choice arrives on your webhook like any other inbound message. What the payload looks like depends on how the number is connected — the same rule as every other inbound event (Which events your number sends).
You receive Meta’s native envelope (webhook field messages), with type: "interactive". The id you set on the row or button comes back, and context.id is the wamid of the message that carried the menu.
A reply button arrives the same way with "type": "button_reply" and "button_reply": { "id": "pay-slip:inv-2041", "title": "Bank slip" }.Branch on interactive.list_reply.id / interactive.button_reply.id — that is your ERP key, untouched.

Match the choice to the menu

Your bot usually has several menus open with different customers. To know which menu a choice belongs to, keep a small record per sent menu:
  1. POST /v1/messages/send returns id → store { id, customer, options }.
  2. Learn the WhatsApp id of that menu: on unofficial numbers, message.sent carries both id and messageId; on official numbers, every statuses[] item carries your id in biz_opaque_callback_data next to the wamid (statuses[].id).
  3. The choice quotes that messageId — quotedMessageId (unofficial) or context.id (official) — so you find the record and the option.
If your options are self-describing ("id": "inv-2041"), step 2 is optional: the row id alone — list_reply.id on official numbers, buttonId on unofficial ones — already tells you what was chosen.

The 24-hour window and cost (official numbers)

  • Free-form messages — buttons, lists, carousels, plain text — only deliver on a Meta number while the 24-hour service window is open, that is, within 24 h of the customer’s last message. A bot that answers inbound messages is always inside it.
  • To start a conversation yourself (a payment reminder, for example), use an approved template first. When the customer replies, the window opens and the bot can continue with lists and buttons.
  • Outside the window the send fails with META_OUTSIDE_24H_WINDOW. You can check the window before sending with GET /v1/service-window.
  • Service messages are no longer always free. Since 2026-10-01, Meta gives 1,000 free service messages per month per number and charges the country’s Utility rate after that. Meta bills it, not Pilot Status. See Pricing.
Unofficial numbers have no window and no Meta charge.

Common mistakes

Next steps