Why this topic confuses so many people
Search for "WhatsApp buttons" and you will find old tutorials showing three reply buttons under a message. That format stopped working reliably outside channels controlled by Meta, and plenty of integrations broke without warning. The result is a market full of promises that fall apart when the customer opens the message.
This page separates what you can ship to production on an unofficial WhatsApp API today, what depends on the device, and what is best avoided.
A map of interactive formats
| Format | Endpoint | Returns a choice? | Where it renders |
|---|---|---|---|
| List message | /interactive/send/list | Yes, as list_response | Phone and desktop |
| Link or call button | /interactive/send/template | No, opens a site or a call | Varies by device |
| NativeFlow | /interactive/send/nativeflow | Depends on the button type | Phone only |
| Carousel | /interactive/send/carousel | Depends on the card buttons | Varies by device |
The rule of thumb: if the customer's choice needs to come back to your system and work on any screen, use a list. The other formats are add-ons.
Lists: the reliable replacement for reply buttons
A list shows a button (the buttonText) that opens a menu with sections and options. It holds 1 to 10 sections, each with 1 to 10 rows. Each row has a rowId, which is what your system gets back, and a title, which is what the customer reads.
import { DApi } from 'd-api-sdk'
const dapi = new DApi({ apiKey: process.env.DAPI_KEY })
await dapi.interactive.sendList({
sessionId: 'downtown-clinic',
to: '14155550123',
title: 'Your appointment tomorrow',
description: 'Dr. Patel, 2:30 PM. How would you like to proceed?',
buttonText: 'Reply',
footerText: 'Downtown Clinic',
sections: [
{
title: 'Schedule',
rows: [
{ rowId: 'confirm', title: 'Confirm attendance' },
{ rowId: 'reschedule', title: 'Reschedule', description: 'We will show new times' },
{ rowId: 'cancel', title: 'Cancel' },
],
},
],
})Use stable rowId values with plain ASCII characters. The title can change per campaign or language; the identifier is the contract with your backend.
How the choice gets back to your system
When the customer taps an option, a messages.received event arrives with type: "list_response". It carries the selected_row_id, the chosen title and a summary of the original list. The typical flow looks like this:
- Your system sends the list and stores the context (which appointment, which order).
- The customer picks an option. The webhook receives the
list_response. - The worker reads the
selected_row_id, matches it with the context for that number and runs the action: confirm, offer new times, cancel. - If the customer types instead of tapping, treat it as free text and resend the list or bring in an agent.
That fourth step is what separates a flow that works from one that gets stuck. Some people will always type "yes", and your system needs to understand that.
Link, call and carousel buttons
Link and call
The /interactive/send/template endpoint sends a message with url or call buttons. It fits "View order", "Track delivery" or "Call the store", and a URL button pointing to a payment link works well in payment reminder flows. Despite the name, it has nothing to do with the approved templates of the official API.
curl -X POST https://api.d-api.cloud/api/v1/interactive/send/template \
-H "Authorization: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"sessionId": "store",
"to": "14155550123",
"content": "Your order 4471 has shipped.",
"buttons": [
{ "type": "url", "title": "Track order", "url": "https://store.com/order/4471" },
{ "type": "call", "title": "Call the store", "phone": "14155550199" }
]
}'NativeFlow
Takes 1 to 4 buttons of type quick_reply, url, call and copy. It is the richest format, but it renders on phones only. Use it when you know your audience is in the mobile app, and always keep a text fallback.
Carousel
The carousel shows cards side by side, each with media, text and up to two buttons. It works well as a product showcase.
Limits worth accepting from day one
- Rendering changes by device and app version. Test on the phone, on WhatsApp Web and on desktop before launching.
- WhatsApp can change formats without notice. Make the message text self-explanatory, so it still makes sense if the buttons do not show up.
- Interactive does not replace consent. Sending a list to someone who did not ask for it is still an unsolicited message.
Lists and buttons are usually the visible piece of a larger flow, with triggers, waits and conditions. If that is your case, see automation flows and, to understand when the official API makes more sense, official vs unofficial API.
