Skip to content
The InfiQ GlossaryFeature

Messages & interactivity

Interactive Reply

The webhook payload a user's tap creates, such as a button_reply or list_reply, carrying the ID you set so your system knows which option was picked.

Also called: button_reply · list_reply · Button reply webhook · Interactive response

InfiQ Glossaryreviewed Practitioner

What is an interactive reply?

An interactive reply is the message your webhook receives when a customer answers an Interactive Message, or a button on a template, by tapping instead of typing. It reaches you through the Messages Webhook like any inbound message, but instead of free text it carries the identifier you attached to the option, so your bot, CRM or inbox knows exactly which choice was made without interpreting language.

The shape depends on what was tapped. A reply button produces a message with type: interactive and interactive.type: button_reply, holding the button's id and title. A row in a List Message produces interactive.type: list_reply with the row's id, title and description. A Quick Reply Button on an approved template is different again: the message has type: button and a button object with payload, the value you attached when you sent the template (otherwise the button text), and text, the label. A WhatsApp Flow returns interactive.type: nfm_reply with the submitted answers as a JSON string in response_json; the Flow Response Message page covers that one. Button and list replies carry context.id, the WAMID of the message that held the tapped control.

An interactive reply is not the same as a Contextual Reply. A contextual reply is any message, typed or not, that quotes an earlier message with WhatsApp's reply gesture; it also shows up as a context object, but its content is whatever the customer wrote. An interactive reply is specifically the structured answer a tap produces. Taps on URL buttons, call buttons and copy-code buttons produce no interactive reply at all, because they act on the customer's phone instead of sending a message back.

Key takeaways

  • An interactive reply is the inbound message a tap creates; it carries the ID you set, so you route on data, not on typed text.
  • Reply-button taps arrive as interactive.button_reply and list picks as interactive.list_reply, each with an id and a title.
  • Taps on a template's quick-reply buttons arrive differently: type: button, with button.payload and button.text.
  • context.id names the message whose control was tapped, which lets you reject stale menus and late taps.
  • Flow submissions come back as nfm_reply with the answers in a response_json string.

Figure 1

What does a button tap look like at your webhook?

8 / 8
{
"object": "whatsapp_business_account",
"entry": [
{
"id": "102290129340398",
"changes": [
{
"field": "messages",
"value": {
"messaging_product": "whatsapp",
"metadata": {
"display_phone_number": "919876543210",
"phone_number_id": "106540352242922"
},
"contacts": [
{
"profile": {
"name": "Meera"
},
"wa_id": "919812345678"
}
],
"messages": [
{
"context": {
"from": "919876543210",
"id": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAERgSU0xPVF9BU0tfODgyMTMA"
},
"from": "919812345678",
"id": "wamid.HBgMOTE5ODEyMzQ1Njc4FQIAEhgUM0E5RjJCN0M0RDVFNkYxMjM0NTYA",
"timestamp": "1760862120",
"type": "interactive",
"interactive": {
"type": "button_reply",
"button_reply": {
"id": "slot:v1:ORD-88213:evening",
"title": "Evening"
}
}
}
]
}
}
]
}
]
}
A real-shaped button_reply webhook from a delivery-time question. Step through the fields a router reads: the type switch, your ID, the message that was tapped and the reply's own WAMID.

Field 8: entry.0.changes.0.value.metadata.phone_number_id — Which of your numbers received the tap; answer from the same one.

An interactive reply in code

// Normalise every kind of tap into one shape your handlers can use.
export function readTap(message) {
const tapped = message.context?.id ?? null; // WAMID of the message that was tapped
if (message.type === "interactive") {
const i = message.interactive;
if (i.type === "button_reply") return tap(i.button_reply.id, i.button_reply.title, tapped);
if (i.type === "list_reply") return tap(i.list_reply.id, i.list_reply.title, tapped);
if (i.type === "nfm_reply") return { action: "flow", data: JSON.parse(i.nfm_reply.response_json), tapped };
}
if (message.type === "button") return tap(message.button.payload, message.button.text, tapped); // template quick reply
return null; // typed text, media, location: not a tap
}
function tap(id, label, tapped) {
const [action, version, ...rest] = String(id).split(":");
return { action, version, ref: rest.join(":"), label, tapped };
}
// In the queue worker that processes messages webhooks:
export async function onMessage(message) {
if (await alreadyProcessed(message.id)) return; // retried webhook
const t = readTap(message);
if (!t) return handleFreeText(message);
if (!(await isStillCurrent(t.tapped))) return sendFreshOptions(message.from);
const handler = handlers[t.action];
return handler ? handler(t, message.from) : logUnknownTap(t);
}

Route on IDs and payloads, check `context.id`, and deduplicate on the inbound message ID.

How does an interactive reply work?

  1. Design the IDs before the copy

    Make IDs namespaced, versioned and self-describing, such as slot:v1:ORD-88213:evening. Button IDs can be up to 256 characters and list row IDs up to 200, so there is room for the state you need.

  2. Parse by message type

    Switch on message.type. For interactive, switch again on interactive.type: button_reply, list_reply or nfm_reply. For button, read the template quick-reply payload.

  3. Check what was tapped

    Look up context.id in the messages you sent. If that menu has expired or been superseded, a slot taken, a price changed, reply with fresh options instead of acting on the old choice.

  4. Make the handler idempotent

    Meta may deliver the same webhook more than once. Skip any inbound id you have already processed, and make the business action itself safe to repeat, such as confirming an order only once.

  5. Answer while the window is open

    A tap is a customer message, so it opens or refreshes the 24-hour customer service window. Reply straight away with the next step as a free-form message.

Common mistakes with an interactive reply

  • Handling only button_reply

    Code written for reply buttons silently ignores list picks and template quick replies, which arrive as list_reply and as type: button. Handle every shape in one router.

  • Routing on the title

    Labels get edited, translated and A/B tested. Route on id or payload, and keep the title only for display and logs.

  • Acting on stale taps

    Old messages stay in the chat, so a customer can tap a slot or price from last week's menu. Check context.id against what you sent, or encode a date or version in the ID, and offer fresh options when it has expired.

  • Not deduplicating

    Webhooks can be delivered more than once, and the same choice can reach you twice. Use Webhook Deduplication on the inbound message ID and make actions such as payments or confirmations idempotent.

  • Waiting for a webhook from a URL button

    URL and call buttons never produce an interactive reply. Measure them with tagged links, landing-page events or call logs.

Example: COD confirmation for a Surat saree seller

A Surat saree seller sends a cash-on-delivery confirmation for order ORD-88213 as a utility template with two quick-reply buttons, attaching the payloads cod:v1:ORD-88213:confirm and cod:v1:ORD-88213:cancel. The buyer taps Confirm; the webhook shows type: button with that payload, the order service marks the order confirmed and the bot sends reply buttons asking for a delivery time. Her tap on Evening arrives as a button_reply with the ID slot:v1:ORD-88213:evening. When the same webhook is delivered again a minute later, the handler finds the message ID already processed and does nothing.

— COD confirmation for a Surat saree seller

Why it matters

Why does an interactive reply matter?

Tapped answers are where WhatsApp automation becomes dependable. Routing on button_reply.id instead of interpreting "yes pls" or "हाँ" lets a bot confirm a cash-on-delivery order, book a slot or escalate to an agent with no language model and no guesswork. That only holds if the handler copes with the three slightly different payload shapes, with webhooks that arrive twice and with customers tapping a menu from last week.

Best practices for an interactive reply

  • Put a version in IDs so a menu redesign does not misroute taps on older messages.
  • Log unknown IDs instead of dropping them; they reveal old menus still being used in customers' chats.
  • Keep personal data out of IDs and payloads, since they pass through webhooks and logs.
  • Confirm the choice back to the customer in your next message so the chat reads clearly later.

Interactive Reply FAQs

What is the difference between button_reply and type button in WhatsApp webhooks?
button_reply comes from reply buttons in a free-form interactive message; type: button comes from a quick-reply button on a template. The first carries id and title inside interactive, the second carries payload and text inside button.
How do I know which message a customer's tap belongs to?
Read context.id on the inbound message. It is the WAMID of the message that held the tapped button or list, so you can match it to the order, ticket or menu version you sent.
Can the same tap reach my webhook twice?
Plan for it. Meta may redeliver a webhook, and older messages stay in the chat where options can be tapped again later. Deduplicate on the inbound message ID and make each action safe to repeat.
Do URL buttons send a webhook when tapped?
No. CTA URL, call and copy-code buttons act on the customer's phone and send nothing back to your webhook. Track them with tagged links or landing-page analytics instead.
Does an interactive reply open the 24-hour customer service window?
Yes. A tap is a message from the customer, so it opens the window or restarts its 24-hour clock, and you can answer with free-form messages straight away.

Try it on the official WhatsApp Business API

InfiQ is an official Meta Business Partner. Start a 7-day free trial — no card needed — and see this running on your own number.