Buttons and lists
A fourth kind of send, alongside template, text and media — exactly one of the four
per request. Tappable replies mean nobody has to type 1 for sales.
These are free-form messages, so the 24-hour window applies. Buttons do not reopen a closed one; only a template does.
Reply buttons
Section titled “Reply buttons”Up to three, with titles of 20 characters. Every title in one message must be different, which this API checks before Meta does.
curl -X POST "https://vaarto.com/api/v1/messages/whatsapp" \ -H "Authorization: Bearer $VAARTO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+919876543210", "interactive": { "type": "button", "header": "Order #1029", "body": "Your order is ready. How would you like it?", "footer": "Reply anytime", "buttons": [ { "id": "deliver", "title": "Deliver" }, { "id": "pickup", "title": "Collect" } ] } }'const res = await fetch("https://vaarto.com/api/v1/messages/whatsapp", { method: "POST", headers: { Authorization: `Bearer ${process.env.VAARTO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ to: "+919876543210", interactive: { type: "button", header: "Order #1029", body: "Your order is ready. How would you like it?", footer: "Reply anytime", buttons: [{ id: "deliver", title: "Deliver", }, { id: "pickup", title: "Collect", }], }, }),});
const { data } = await res.json();import os, requests
res = requests.post( "https://vaarto.com/api/v1/messages/whatsapp", headers={"Authorization": f"Bearer {os.environ['VAARTO_API_KEY']}"}, json={ "to": "+919876543210", "interactive": { "type": "button", "header": "Order #1029", "body": "Your order is ready. How would you like it?", "footer": "Reply anytime", "buttons": [{ "id": "deliver", "title": "Deliver", }, { "id": "pickup", "title": "Collect", }], }, }, timeout=10,)
data = res.json()["data"]$data = Http::withToken(env('VAARTO_API_KEY')) ->post('https://vaarto.com/api/v1/messages/whatsapp', [ 'to' => '+919876543210', 'interactive' => [ 'type' => 'button', 'header' => 'Order #1029', 'body' => 'Your order is ready. How would you like it?', 'footer' => 'Reply anytime', 'buttons' => [[ 'id' => 'deliver', 'title' => 'Deliver', ], [ 'id' => 'pickup', 'title' => 'Collect', ]], ], ]) ->json('data');A list holds up to ten rows across all of its sections.
curl -X POST "https://vaarto.com/api/v1/messages/whatsapp" \ -H "Authorization: Bearer $VAARTO_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "to": "+919876543210", "interactive": { "type": "list", "body": "Which size?", "button": "View sizes", "sections": [ { "title": "Drinks", "rows": [ { "id": "sku_s", "title": "Small", "description": "250ml" }, { "id": "sku_l", "title": "Large", "description": "500ml" } ] } ] } }'const res = await fetch("https://vaarto.com/api/v1/messages/whatsapp", { method: "POST", headers: { Authorization: `Bearer ${process.env.VAARTO_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ to: "+919876543210", interactive: { type: "list", body: "Which size?", button: "View sizes", sections: [{ title: "Drinks", rows: [{ id: "sku_s", title: "Small", description: "250ml", }, { id: "sku_l", title: "Large", description: "500ml", }], }], }, }),});
const { data } = await res.json();import os, requests
res = requests.post( "https://vaarto.com/api/v1/messages/whatsapp", headers={"Authorization": f"Bearer {os.environ['VAARTO_API_KEY']}"}, json={ "to": "+919876543210", "interactive": { "type": "list", "body": "Which size?", "button": "View sizes", "sections": [{ "title": "Drinks", "rows": [{ "id": "sku_s", "title": "Small", "description": "250ml", }, { "id": "sku_l", "title": "Large", "description": "500ml", }], }], }, }, timeout=10,)
data = res.json()["data"]$data = Http::withToken(env('VAARTO_API_KEY')) ->post('https://vaarto.com/api/v1/messages/whatsapp', [ 'to' => '+919876543210', 'interactive' => [ 'type' => 'list', 'body' => 'Which size?', 'button' => 'View sizes', 'sections' => [[ 'title' => 'Drinks', 'rows' => [[ 'id' => 'sku_s', 'title' => 'Small', 'description' => '250ml', ], [ 'id' => 'sku_l', 'title' => 'Large', 'description' => '500ml', ]], ]], ], ]) ->json('data');Handling the tap
Section titled “Handling the tap”The tap comes back as an ordinary message.received webhook, with text
set to the button’s label and details.replyId set to the id you chose.
Branch on the id, never on the text. The label is copy somebody may reword; the id is the contract.
// 1. Ask.await fetch("https://vaarto.com/api/v1/messages/whatsapp", { method: "POST", headers: { Authorization: auth, "Content-Type": "application/json" }, body: JSON.stringify({ to: customer, interactive: { type: "button", body: "Your order is ready. How would you like it?", buttons: [ { id: "deliver", title: "Deliver" }, { id: "pickup", title: "Collect" }, ], }, }),});
// 2. The tap arrives on your webhook as a normal message.received.function onInbound(data) { switch (data.details?.replyId) { case "deliver": return arrangeDelivery(data.from); case "pickup": return sendPickupCode(data.from); default: return handleFreeText(data.text); }}details is ours and metadata is yours. Nothing the platform knows about a message is
ever written into metadata, so your own JSON reads back exactly as you sent it — even on
a message carrying buttons.