Documentation
Sign inGet started

WhatsApp interactive messages

An interactive message is body text plus something for the recipient to tap: a WhatsApp button, a menu, a link, a card, or a request for their location or contact details. Where a template answer means parsing free text, a WhatsApp menu or a set of WhatsApp buttons gives the recipient a fixed set of choices and gives you back a value you defined. This page covers what the six types share; each type's own page covers its wire shape and its own limits.

The six types

TypeBird interactive.typeHeaderFooterBody max
Reply buttonsbuttontext, image, video, documentyes1024
List menuslisttext onlyyes4096
Link buttonscta_urltext, image, video, documentyes1024
Media carouselscarouselnone on the message; image or video per cardno1024 message, 160 per card
Location requestslocation_request_messagenoneno1024
Contact info requestsrequest_contact_infononeno1024
Every type is free-form: deliverable only inside an open customer service window, and never reviewed by Meta the way a template is.
Interactive messages are free-form content, so the customer service window rule applies: see the customer service window for what that means and what a closed window returns.
Every interactive send also requires from, a number your workspace owns. Bird's managed numbers cannot carry it, so an interactive send needs a number of your own connected first.

The interactive content arm

interactive is one of the mutually exclusive content fields on POST /v1/whatsapp/messages, beside template, text, image, and the rest: exactly one may be present on a send. Inside interactive, type names which of the six variants this is, and that variant's own field carries the rest (buttons, list, cta_url, or cards). The schema bars every other variant's field, so mixing two variants on one send fails validation before it reaches a handler.
For the request envelope, the 202 response model, and safe retries, see How sending works rather than this page re-teaching them.
Here is a minimal interactive message: two WhatsApp buttons on a reply-buttons send, one language at a time.
const msg = await bird.whatsapp.send({
  to: "+15551234567",
  from: "+13124495648",
  interactive: {
    type: "button",
    body_text: "Your gardening workshop is scheduled for 9am tomorrow.",
    buttons: [
      { type: "quick_reply", quick_reply: { slug: "change-booking", text: "Change" } },
      { type: "quick_reply", quick_reply: { slug: "cancel-booking", text: "Cancel" } },
    ],
  },
});
console.log(msg.id, msg.status);

Buttons

Four of the six types place a button, and all of them draw on the same shape: a discriminated object whose type is quick_reply or cta_url, each carrying its own nested field of the same name. A quick_reply button carries slug and text; a cta_url button carries text and url. Which types accept which button shape:
  • Reply buttons send only quick_reply buttons, 1 to 3 of them.
  • Link buttons send exactly one cta_url button.
  • Media carousels put buttons on each card: either one cta_url button, or up to three quick_reply buttons, and every card in the carousel must agree.
  • List menus use rows inside sections rather than this button object, covered on their own page.
A quick_reply button's slug is your own handle for that button. It is never shown to the recipient, only its text label is, and the slug is echoed verbatim on the reply. That round trip is what makes a reply correlatable to the button that produced it, so this is worth saying once, here, rather than on each leaf page.

Reading a reply

Pressing a button or choosing a menu row sends its own inbound message, carrying an interactive_reply object. interactive_reply.type is button or list; whichever it is, the nested object carries the slug and text you declared, the tapped label the recipient actually saw. The two request types, location requests and contact info requests, answer differently: a location request's reply is an ordinary inbound location message, and a contact info request's reply is an inbound contact card, not an interactive_reply at all.
A reply reaches you through the message list and GET /v1/whatsapp/messages/{id}, the same way any inbound WhatsApp message does. To act on one as it arrives rather than polling, subscribe to the whatsapp.received webhook: its payload carries interactive_reply, so it already names the button or row that was tapped.

Quoting a message to correlate a reply

in_reply_to_message_id on a send quotes an earlier message from the same conversation, and every message, sent or received, echoes it back on a read. It is one field for both directions.
The correlation this buys you is asymmetric. A tap on a WhatsApp button or menu row carries Meta's own context, so in_reply_to_message_id resolves to the message that offered it. A shared contact card carries no context at all, so it resolves to nothing: you correlate a contact info request's reply on from and timing, not on this field.
Resolution goes through a message-context store, and a miss omits the field rather than reporting one. That is indistinguishable, on the wire, from a reply that answers nothing at all. An integration that needs reliable correlation should not rely on this field alone: carry your own metadata on the send and match on that instead.
The window a message stays quotable in is limited; see E15057 for what happens once it closes. Sending WhatsApp messages owns the send-side field: its length, its resolution, and the request shape.

Errors

Five error codes are specific to interactive content. Three of the five only fire on the types that have the field they check, so the fourth column names which types can actually reach each one.
CodeStatusWhat triggers itApplies to
E15055 WhatsAppInteractiveLimitExceeded422The message exceeds a limit for its type; today, more than 10 rows across a list's sections.List menus only
E15056 WhatsAppInteractiveDuplicateLabel422Two buttons or rows in the same message share a label.Any type with labelled buttons or rows: reply buttons, list menus, media carousels
E15059 WhatsAppInteractiveCarouselButtonsMismatch422A carousel's cards do not all carry the same buttons.Media carousels only
E15057 WhatsAppInReplyToNotFound422The quoted message is not one this workspace holds.Any type, when it carries in_reply_to_message_id
E15058 WhatsAppInReplyToNotQuotable422The quoted message cannot be quoted.Any type, when it carries in_reply_to_message_id
Every interactive send can also hit the errors any WhatsApp send can: a closed customer service window, a missing or invalid sender, an invalid recipient, or ambiguous content. Those are shared across every WhatsApp content type, not specific to interactive messages; see How sending works for that list rather than a copy of it here.

Next steps