← Blog

·

WhatsApp API interactive messages: buttons, lists, media, location and Flows

Updated on October 29, 2026

Interactive messages are sends with type: "interactive" that show the user reply buttons (up to 3), a list (up to 10 options), a link button, a location request, a carousel or a WhatsApp Flow; when the user taps, the webhook returns the id you defined. They are service messages, so they can only be sent while the 24-hour window is open. To start a conversation with buttons or a Flow, use a template.

Below are the payloads for each type, the character limits, the webhook reply format and a Node.js router to handle them all. The examples use Vectalk (https://apis.vectalk.com.br/api/messages), which accepts the same body as Meta’s WhatsApp Cloud API.

Overview of the types

Typeinteractive.typeMain limitsWhat comes back in the webhook
Reply buttonsbuttonUp to 3 buttons; 20-character label; 1,024-character bodyinteractive.button_reply with id and title
ListlistUp to 10 sections and 10 rows in total; 20-character buttoninteractive.list_reply with id, title and description
Link buttoncta_url1 button; 20-character labelNothing (opens the browser)
Location requestlocation_request_message1,024-character bodyA type: "location" message
Media carouselcarousel2 to 10 cards with image or videoDepends on the button used
Flowflowflow_message_version equal to "3"interactive.nfm_reply with response_json
Product and multi-productproduct, product_listTied to a catalogAn order (type: "order")

Plain media (image, video, document, audio) and fixed locations aren’t interactive, but they usually show up in the same flows and are covered in this guide too.

Reply buttons

The most common format for confirming, choosing among a few options or routing to an agent.

curl -X POST https://apis.vectalk.com.br/api/messages \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "messaging_product": "whatsapp",
    "to": "5584999999999",
    "type": "interactive",
    "interactive": {
      "type": "button",
      "header": { "type": "text", "text": "Tomorrow's appointment" },
      "body": { "text": "Hi Marina. Your appointment is booked for Oct 21 at 9 a.m. Can you confirm?" },
      "footer": { "text": "Example Clinic" },
      "action": {
        "buttons": [
          { "type": "reply", "reply": { "id": "confirm_8812", "title": "Confirm" } },
          { "type": "reply", "reply": { "id": "reschedule_8812", "title": "Reschedule" } },
          { "type": "reply", "reply": { "id": "agent", "title": "Talk to someone" } }
        ]
      }
    }
  }'

Meta’s rules for this type:

  • up to 3 buttons, with unique labels of up to 20 characters;
  • id of up to 256 characters;
  • body of up to 1,024 characters and footer of up to 60;
  • optional header of text, image, video or document.

Use the id to carry context. In the example, confirm_8812 already says the action and the appointment, so the webhook doesn’t depend on the button text. When the user taps, you get:

"interactive": {
  "type": "button_reply",
  "button_reply": { "id": "confirm_8812", "title": "Confirm" }
}

Want to try it while you read? Create a Vectalk account and send your first message through the official API. Create free account →

Interactive list

For more than three options: menus, time slots, departments, plans. The user taps the button and a panel with the rows opens.

{
  "messaging_product": "whatsapp",
  "to": "5584999999999",
  "type": "interactive",
  "interactive": {
    "type": "list",
    "header": { "type": "text", "text": "Available times" },
    "body": { "text": "Pick a time for your technical visit." },
    "footer": { "text": "Brasília time" },
    "action": {
      "button": "See times",
      "sections": [
        {
          "title": "Wednesday, Oct 22",
          "rows": [
            { "id": "slot_2210_09", "title": "09:00", "description": "Technician available" },
            { "id": "slot_2210_14", "title": "14:00", "description": "Technician available" }
          ]
        },
        {
          "title": "Thursday, Oct 23",
          "rows": [
            { "id": "slot_2310_10", "title": "10:00" }
          ]
        }
      ]
    }
  }
}
FieldLimit
action.button20 characters, one button
SectionsUp to 10, title up to 24 characters
RowsUp to 10 across all sections
rows[].title24 characters
rows[].description72 characters, optional
rows[].id200 characters
Body4,096 characters
HeaderText only, 60 characters
Footer60 characters, optional

The 10-row limit is the total for the message, not per section. If you have more options, split them into steps (first the day, then the time). The choice comes back as list_reply:

"interactive": {
  "type": "list_reply",
  "list_reply": { "id": "slot_2210_14", "title": "14:00", "description": "Technician available" }
}

Link button (CTA URL)

Hides a long URL behind a button. Useful for payment slips, tracking and customer portals. (In Brazil, a common use is the boleto, a bank payment slip customers pay by barcode.)

{
  "messaging_product": "whatsapp",
  "to": "5584999999999",
  "type": "interactive",
  "interactive": {
    "type": "cta_url",
    "body": { "text": "Your duplicate invoice is ready." },
    "action": {
      "name": "cta_url",
      "parameters": {
        "display_text": "Open invoice",
        "url": "https://customer.example.com/invoices/7731?source=whatsapp"
      }
    }
  }
}

The label can be up to 20 characters and the body up to 1,024. The tap opens the browser and doesn’t generate a user message, so it doesn’t renew the 24-hour window. To measure clicks, use parameters in the URL itself.

Location: sending and requesting

To send a fixed address (store, pickup point), use type: "location". Latitude and longitude are required; name and address are optional.

{
  "messaging_product": "whatsapp",
  "to": "5584999999999",
  "type": "location",
  "location": {
    "latitude": -5.7945,
    "longitude": -35.2110,
    "name": "Example Store Downtown",
    "address": "Av. Exemplo, 100 - Natal, RN"
  }
}

To ask for the user’s location (delivery, field service), use the interactive message with the send location button:

{
  "messaging_product": "whatsapp",
  "to": "5584999999999",
  "type": "interactive",
  "interactive": {
    "type": "location_request_message",
    "body": { "text": "To calculate shipping, tap the button and send your location." },
    "action": { "name": "send_location" }
  }
}

The reply arrives as a regular location message, with latitude, longitude and, if the user chooses to share them, name and address. The context.id field points to the request message, which helps tie the reply to the right request.

Media carousel

The carousel shows 2 to 10 horizontally scrolling cards. Each card needs an image or video header, can have a body of up to 160 characters (with up to 2 line breaks) and must have either a link button or quick reply buttons, with the same button type and count on every card. It works well for showing product variations. The full structure is on the carousel page of Meta’s documentation; to start a conversation with cards, see the WhatsApp carousel template.

WhatsApp Flows

Flows are multi-screen forms inside WhatsApp: sign-ups, bookings, surveys, credit simulations. The screen content is defined in a JSON file (Flow JSON), and the result comes back to you in the webhook. For a broader introduction, see WhatsApp Flows.

Meta lists two prerequisites: a verified business and consistently high message quality.

1. Create, upload the JSON and publish

With Vectalk, the cycle runs via API with an instance token:

# Create the Flow as a draft
curl -X POST https://apis.vectalk.com.br/api/flows \
  -H "Authorization: Bearer YOUR_INSTANCE_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "visit_booking", "categories": ["APPOINTMENT_BOOKING"] }'

# Upload flow.json (multipart)
curl -X POST https://apis.vectalk.com.br/api/flows/FLOW_ID/assets \
  -H "Authorization: Bearer YOUR_INSTANCE_TOKEN" \
  -F "file=@flow.json" -F "name=flow.json" -F "asset_type=FLOW_JSON"

# Publish
curl -X POST https://apis.vectalk.com.br/api/flows/FLOW_ID/publish \
  -H "Authorization: Bearer YOUR_INSTANCE_TOKEN"

The upload response includes validation_errors. Fix everything before publishing: a published Flow can’t be deleted, only deprecated.

2. Send the Flow inside the window

{
  "messaging_product": "whatsapp",
  "to": "5584999999999",
  "type": "interactive",
  "interactive": {
    "type": "flow",
    "header": { "type": "text", "text": "Book a visit" },
    "body": { "text": "Pick a date, time and address in a few steps." },
    "footer": { "text": "Takes about 1 minute" },
    "action": {
      "name": "flow",
      "parameters": {
        "flow_message_version": "3",
        "flow_id": "FLOW_ID",
        "flow_token": "bk_5584999999999_20261020",
        "flow_cta": "Book",
        "flow_action": "navigate",
        "flow_action_payload": { "screen": "START", "data": { "name": "Marina" } }
      }
    }
  }
}

The flow_token is yours: use it to identify the session when the reply comes back. Meta recommends a CTA of 30 characters or less, with no emoji. To start a conversation with a Flow outside the window, create a template with a FLOW button, as explained in the guide to WhatsApp API message templates.

3. Receive the reply

When the user finishes, you get an interactive message of type nfm_reply. The response_json is a string with the data defined in the Flow’s completion action:

"interactive": {
  "type": "nfm_reply",
  "nfm_reply": {
    "name": "flow",
    "body": "Sent",
    "response_json": "{\"flow_token\":\"bk_5584999999999_20261020\",\"date\":\"2026-10-22\",\"time\":\"14:00\"}"
  }
}

Keep an eye on the Flow’s health. If the Flow’s endpoint becomes unstable, Meta changes the status to THROTTLED (sending limited to 10 messages per hour) and, if it gets worse, to BLOCKED, when it can be neither sent nor opened. The changes arrive through the flows webhook.

Media: image, video, document and audio

Media goes by link (a public URL) or by id (obtained on upload). With Vectalk, the upload is done at /api/media and returns the media_id. More examples are in how to send media with the WhatsApp API.

{
  "messaging_product": "whatsapp",
  "to": "5584999999999",
  "type": "document",
  "document": {
    "link": "https://files.example.com/contracts/7731.pdf",
    "filename": "contract-7731.pdf",
    "caption": "Here is the contract for signature."
  }
}

Media IDs returned by the upload are valid for 30 days, so you can reuse the same id across sends instead of uploading the file again. For media headers on buttons, Meta advises using the uploaded media id instead of a link. To handle the files customers send you, see how to download media from the WhatsApp webhook.

Routing replies in the webhook

A simple router for the types above, taking advantage of the fact that Vectalk delivers Meta’s payload unchanged:

function route(msg) {
  if (msg.type === "location") {
    return calculateShipping(msg.from, msg.location.latitude, msg.location.longitude, msg.context?.id);
  }
  if (msg.type !== "interactive") return handleFreeText(msg);

  const i = msg.interactive;
  switch (i.type) {
    case "button_reply":
      if (i.button_reply.id === "agent") return handOffToHuman(msg.from);
      return runAction(msg.from, i.button_reply.id);
    case "list_reply":
      return bookSlot(msg.from, i.list_reply.id);
    case "nfm_reply": {
      const data = JSON.parse(i.nfm_reply.response_json);
      return saveBooking(data.flow_token, data);
    }
    default:
      return logUnhandled(msg);
  }
}

Replies to template buttons (quick reply) arrive with type: "button", a different format from button_reply; handle both. The guide to WhatsApp API webhooks shows the full server with a queue and deduplication, the Node.js WhatsApp bot tutorial builds one from scratch, and the article on an AI chatbot with the WhatsApp API shows how to combine buttons with AI-generated replies.

How to choose the right type

The choice depends on what you need back:

NeedRecommended typeWhy
Yes or no, confirm or rescheduleReply buttonsOne tap, a structured reply and a renewed window
Choose among 4 to 10 optionsListFits in a single message, with a description per row
Send to payment, tracking or a portalLink buttonHides the URL and keeps the text clean
Delivery or visit addressLocation requestExact coordinates, no typing
Collect several fields (name, date, address, preferences)FlowScreens with validation, no back-and-forth in the chat
Show product variationsCarousel or catalog messagesSide-by-side visual comparison

If the conversation needs more than two questions in a row, a Flow is usually shorter for the user than several buttons in sequence.

Best practices

  • Short labels with a verb. “Confirm” and “Reschedule” work better than sentences. The 20-character limit cuts off the rest.
  • Always a way out to a person. The WhatsApp Business Messaging Policy requires prompt, clear and direct escalation paths in automated experiences, such as handing off to a human agent. A “Talk to someone” button solves it.
  • Stable IDs. Treat the id as a contract with your backend. Changing the button text shouldn’t break routing.
  • Accept free text. Users may ignore the buttons and type. The router needs to understand “yes”, “2 p.m. works” or a voice note.
  • Don’t count on delivery order. Meta warns that the delivery order of a sequence of messages isn’t guaranteed. If order matters, wait for the previous message’s delivered status before sending the next.
  • One question per message. Three interactive messages in a row confuse people and lead to replies to the wrong message. The webhook’s context.id shows which message the user replied to.

Frequently asked questions

Can I send buttons to a customer who hasn’t talked to me today?

Not as an interactive message. Outside the 24-hour window, use a template with quick reply, link or Flow buttons.

Can I add more than 3 reply buttons?

Not in an interactive message. Use a list (up to 10 options). In templates, the limit is up to 10 buttons, with rules per type.

Are interactive messages charged?

They are service messages. Since October 1, 2026, Meta gives each number 1,000 free delivered service messages per month and charges after that, except inside the free entry point window opened by an ad. Rates vary by the recipient’s country; see WhatsApp API pricing by country.

Do I need my own endpoint to use Flows?

Not for simple Flows, where the data lives in the Flow JSON itself and comes back in nfm_reply. Flows that fetch real-time data (available times, prices) use the data_exchange action and require an endpoint of your own.

Build interactive conversations with Vectalk

With Vectalk, buttons, lists, location, media and Flows use the same payload as the Cloud API, and replies arrive in the webhook in the format of Meta’s documentation. One token covers all your accounts, coexistence with the WhatsApp Business app is available, and billing works with a monthly fee plus a prepaid balance: Vectalk puts its own line of credit on your number and each delivered message is deducted from the balance, with no card registered with Meta. Create your account at apps.vectalk.com.br/register, see the examples at docs.vectalk.com.br or talk to the team in the Help Center.


Sources checked on October 29, 2026: Reply button messages, List messages, CTA URL messages, Location request messages, Media carousel messages, Sending a Flow, Flow health and monitoring, Media.

Try the official WhatsApp API today

Create your account, generate a token and send your first message with the quick-start guide. Same payloads and webhooks as Meta's Cloud API.