WhatsApp reply buttons
Reply buttons put up to three tappable choices under a WhatsApp message, so the recipient answers with a tap instead of free text. Use them for a quick decision, like confirming or cancelling a booking. For more than three choices, use list menus instead.
Send reply buttons
Set interactive.type to button, with a body_text and one to three buttons, each a quick_reply:
const msg = await bird.whatsapp.send({
to: "+16505551234",
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" } }],
},
});
console.log(msg.id, msg.status);msg = client.whatsapp.send(
to="+16505551234",
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"}}],
},
)
print(msg.id, msg.status)package main
import (
"context"
"fmt"
"log"
"os"
bird "github.com/messagebird/bird-sdk-go"
"github.com/messagebird/bird-sdk-go/option"
)
func main() {
client, err := bird.NewClient(option.WithAPIKey(os.Getenv("BIRD_API_KEY")))
if err != nil {
log.Fatal(err)
}
msg, err := client.Whatsapp.Send(context.Background(), bird.WhatsappSendParams{
To: "+16505551234",
From: "+13124495648",
Interactive: &bird.WhatsAppInteractiveSend{
Type: "button",
BodyText: "Your gardening workshop is scheduled for 9am tomorrow.",
Buttons: &[]bird.WhatsAppInteractiveButtonSend{
{Type: "quick_reply", QuickReply: &bird.WhatsAppInteractiveQuickReplyButtonSend{Slug: "change-booking", Text: "Change"}},
},
},
})
if err != nil {
log.Fatal(err)
}
fmt.Println(msg.Id, *msg.Status)
}$interactive = (new WhatsAppMessageSendRequestInteractive())
->setType('button')
->setBodyText('Your gardening workshop is scheduled for 9am tomorrow.')
->setButtons([
(new WhatsAppInteractiveButtonSend())
->setType('quick_reply')
->setQuickReply((new WhatsAppInteractiveButtonSendQuickReply())->setSlug('change-booking')->setText('Change')),
]);
$message = $bird->whatsapp->send(
to: '+16505551234',
from: '+13124495648',
interactive: $interactive,
);
echo $message->getId(), ' ', $message->getStatus();bird whatsapp send \
--from +13124495648 \
--interactive '{"body_text":"Your gardening workshop is scheduled for 9am tomorrow.","buttons":[{"quick_reply":{"slug":"change-booking","text":"Change"},"type":"quick_reply"}],"type":"button"}' \
--to +16505551234curl -X POST "https://{region}.platform.bird.com/v1/whatsapp/messages" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"to": "+16505551234",
"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" } }
]
}
}'from is required on every service message: a number your workspace owns, not a Bird-managed one. The full shape adds an optional header, footer, a quote of an earlier message, and a second button:
Code example
{
"to": "+16505551234",
"from": "+13124495648",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"interactive": {
"type": "button",
"header": {
"type": "image",
"url": "https://cdn.example.com/banners/workshop.png"
},
"body_text": "Your gardening workshop is scheduled for 9am tomorrow.",
"footer_text": "Lucky Shrub, your gateway to succulents",
"buttons": [
{ "type": "quick_reply", "quick_reply": { "slug": "change-booking", "text": "Change" } },
{ "type": "quick_reply", "quick_reply": { "slug": "cancel-booking", "text": "Cancel" } }
]
},
"tags": [{ "name": "category", "value": "booking" }],
"metadata": { "order_id": "A-1" }
}in_reply_to_message_id quotes an earlier message in the same conversation. See the hub's quoting a message to correlate a reply for how resolution works and what it can miss.
This type sends only quick_reply buttons. A cta_url button belongs to a separate interactive.type and cannot appear alongside buttons; see the hub's buttons section for the shared button shape.
Headers and footers
A header is optional, and it is one of four shapes:
Code example
"header": { "type": "text", "text": "New workshop dates" }
"header": { "type": "image", "url": "https://cdn.example.com/a.png" }
"header": { "type": "video", "url": "https://cdn.example.com/a.mp4" }
"header": { "type": "document", "url": "https://cdn.example.com/a.pdf" }A media header (image, video, or document) carries its file as a public https URL that WhatsApp fetches at send time, rather than an uploaded media handle. footer_text is optional and adds a line below the buttons.
Limits
| Field | Bound |
|---|---|
| buttons | 1 to 3 entries, every one a quick_reply |
| quick_reply.slug | required, 1 to 256 characters |
| quick_reply.text (label) | required, 1 to 20 characters, unique within the message |
| body_text | required, 1 to 1024 characters |
| footer_text | optional, 1 to 60 characters |
| header.text | 1 to 60 characters |
Bird checks that button labels (quick_reply.text) are unique, but it does not check that slug values are unique, even though each slug is meant to identify one button. Two buttons sharing a slug both send and deliver, and their replies come back indistinguishable.
Reading the reply
A press arrives as its own inbound message, carrying interactive_reply:
Code example
{
"id": "wam_01kyb2m4xq7whs0d8n3prv6tez",
"direction": "inbound",
"from": { "phone_number": "+16505551234" },
"to": { "phone_number": "+13124495648" },
"status": "received",
"in_reply_to_message_id": "wam_01kya19eknftrs2s6p82asmvnh",
"interactive_reply": {
"type": "button",
"button": {
"slug": "cancel-booking",
"text": "Cancel"
}
},
"created_at": "2026-08-25T09:04:11Z"
}The slug you set on the send comes back verbatim, so you can branch on it directly without a lookup table. You see this reply through the message list or GET /v1/whatsapp/messages/{id}; see the hub's reading a reply for that path in full.
Limits and edge cases
- The customer service window has to be open. Reply buttons are a service message, deliverable only inside an open window; see the hub's customer service window. The window check fails open, so a 202 is not proof the window was actually open when the send goes out.
- from must be a number your workspace owns. Omitting it, or naming a number that isn't a connected sender, is rejected before the send is created.
- Labels must be unique, or the send is refused. Two buttons with the same quick_reply.text fail with 422 E15056 WhatsAppInteractiveDuplicateLabel, because Meta would otherwise reject the duplicate after the send is already accepted and charged.
- The label is what the recipient sees; the slug never is. Putting user-facing copy in slug is a silent no-op, since only text renders in the chat.
- A media header URL that WhatsApp can't fetch fails after the send is accepted. Bird does not validate the header url the way it validates a media message's URL, so an http:// URL or one that returns an error passes the request and then fails asynchronously, with media_rejected on the message's last_error.
- Sending Meta's own field names fails the request. This type rejects unknown properties outright, so JSON copied from Meta's Cloud API reference, such as a body object or an action.buttons wrapper, needs reshaping into Bird's flat fields first.
A quote that doesn't resolve fails the request before anything is created or charged: 404 E15071 when the id names no message this workspace holds, 422 E15072 when it names one that cannot be quoted. For the errors any WhatsApp send can hit, a closed window, a missing or invalid sender, or an invalid recipient, see the hub's errors and Sending WhatsApp messages.
Next steps
- WhatsApp interactive messages: what all six interactive types share
- List menus: for more than three choices
- Sending WhatsApp messages: the request envelope, the 202 model, and safe retries
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.
Watch the guideConnecting WhatsApp to Bird: from buying a number to a live channelUnderstand the conceptWhat is the 24-hour customer service window on WhatsApp?Use the toolWhatsApp message builderExplore the capabilityWhatsApp
Try the practice and get an implementation brief