When to Use
- Customer survey or NPS: A post-service workflow sends a button message with three options — "Great", "Okay", "Poor" — to collect a quick satisfaction rating without the customer needing to type anything.
- Appointment slot selection: A booking workflow sends a list message with available time slots grouped by day, allowing the customer to pick their preferred appointment time from a structured picker.
- Support triage: A customer support workflow presents three buttons — "Order issue", "Billing question", "Technical help" — to route the customer to the right team before a human agent responds.
- Product variant selection: An e-commerce workflow sends a list picker with product sizes and colours, allowing the customer to make a selection before confirming an order via WhatsApp.
- Confirmation request: An approval workflow sends a button message to a manager with "Approve" and "Reject" buttons when an expense report is submitted, enabling a single-tap approval decision.
Two interactive types in one operation: This node handles both button messages (up to 3 quick-reply buttons) and list messages (a menu opened by tapping a single button). The type is determined by which JSON schema you provide — buttonsJson for button type, sectionsJson + buttonText for list type.
Configuration
Connection
| Field | Required | Description |
accessToken | Required | Permanent System User access token. Store in BizFirst Credentials Manager. |
phoneNumberId | Required | Numeric string ID of the sending WhatsApp Business phone number. |
apiVersion | Optional | Meta Graph API version. Defaults to v18.0. |
Operation — Button Type
| Field | Required | Description |
to | Required | Recipient's phone number with country code, no + prefix. |
bodyText | Required | The main message text displayed above the buttons. Maximum 1024 characters. |
buttonsJson | Required | JSON array string of button objects. Maximum 3 buttons. Each button: { "type": "reply", "reply": { "id": "btn_yes", "title": "Yes" } }. The id is returned in the webhook when the user taps; title is the display label (max 20 chars). |
headerText | Optional | Text displayed in bold above the body. Maximum 60 characters. |
footerText | Optional | Small grey text below the buttons. Maximum 60 characters. Use for disclaimers or context. |
Operation — List Type
| Field | Required | Description |
to | Required | Recipient's phone number with country code, no + prefix. |
bodyText | Required | The main message text displayed above the list button. |
buttonText | Required | Label of the button that opens the list picker (e.g. "Choose a time slot"). Maximum 20 characters. |
sectionsJson | Required | JSON array string of section objects. Each section: { "title": "Morning", "rows": [{ "id": "slot_9am", "title": "9:00 AM", "description": "Available" }] }. Maximum 10 sections, 10 rows per section. Row id is returned in webhook on selection. |
headerText | Optional | Bold text above the body. |
footerText | Optional | Small grey footer text. |
Sample Configuration — Button
{
"resource": "message",
"operation": "sendInteractiveButtons",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"headerText": "Order Confirmation",
"bodyText": "Your order #{{ $input.orderId }} is ready to ship. Please confirm your delivery address:\n\n{{ $input.address }}",
"buttonsJson": "[{\"type\":\"reply\",\"reply\":{\"id\":\"confirm_address\",\"title\":\"Address is correct\"}},{\"type\":\"reply\",\"reply\":{\"id\":\"update_address\",\"title\":\"Update address\"}},{\"type\":\"reply\",\"reply\":{\"id\":\"cancel_order\",\"title\":\"Cancel order\"}}]",
"footerText": "Reply expires in 24 hours"
}
Sample Configuration — List
{
"resource": "message",
"operation": "sendInteractiveList",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.patientPhone }}",
"headerText": "Book Appointment",
"bodyText": "Please select a time slot for your appointment with Dr. {{ $input.doctorName }}.",
"buttonText": "View available slots",
"sectionsJson": "[{\"title\":\"Tomorrow (Monday)\",\"rows\":[{\"id\":\"slot_mon_9\",\"title\":\"9:00 AM\",\"description\":\"45-minute consultation\"},{\"id\":\"slot_mon_11\",\"title\":\"11:00 AM\",\"description\":\"45-minute consultation\"},{\"id\":\"slot_mon_2\",\"title\":\"2:00 PM\",\"description\":\"45-minute consultation\"}]},{\"title\":\"Tuesday\",\"rows\":[{\"id\":\"slot_tue_10\",\"title\":\"10:00 AM\",\"description\":\"45-minute consultation\"},{\"id\":\"slot_tue_3\",\"title\":\"3:00 PM\",\"description\":\"45-minute consultation\"}]}]",
"footerText": "We will send a confirmation once you select"
}
Validation Errors
| Error | Cause |
accessToken is required | The accessToken field is empty. |
phoneNumberId is required | The phoneNumberId field is empty. |
to is required | The to field is empty. |
bodyText is required | The bodyText field is empty. |
buttonsJson is required | The buttonsJson field is empty (button type). |
Invalid JSON in buttons: ... | The buttonsJson string is malformed JSON. |
Invalid JSON in sections: ... | The sectionsJson string is malformed JSON. |
131009 (Meta) | More than 3 buttons provided, or button title exceeds 20 characters. |
131047 (Meta) | 24-hour customer service window expired. |
Output
Success Port
| Field | Type | Description |
messageId | string | Unique WhatsApp message ID (wamid). Store this to correlate with the webhook reply containing the user's button/list selection. |
phoneNumberId | string | The sending phone number ID. |
to | string | The recipient phone number. |
status | string | "sent" on success. |
errorCode | string | Empty on success. |
errorMessage | string | Empty on success. |
rawResponse | string | Full raw JSON from the Meta API. |
Error Port
| Field | Type | Description |
status | string | "failed" or "error". |
errorCode | string | Meta error code or BizFirst validation code. |
errorMessage | string | Human-readable error description. |
rawResponse | string | Raw API error response. |
Sample Output
{
"messageId": "wamid.HBgLMTQxNTU1NTI2NzEVAgARGBIxRTI4QUY4NjhFMEI4NDgCCA",
"phoneNumberId": "123456789012345",
"to": "14155552671",
"status": "sent",
"errorCode": null,
"errorMessage": null,
"rawResponse": "{\"messaging_product\":\"whatsapp\",\"contacts\":[{\"input\":\"14155552671\",\"wa_id\":\"14155552671\"}],\"messages\":[{\"id\":\"wamid.HBgLMTQxNTU1NTI2NzEVAgARGBIxRTI4QUY4NjhFMEI4NDgCCA\"}]}"
}
Expression Reference
| Value | Expression | Notes |
| Message ID | {{ $output.sendInteractive.messageId }} | Store this. When the user taps a button, the webhook delivers their selection with the context message ID — correlate to identify which interactive message they responded to. |
| Recipient | {{ $output.sendInteractive.to }} | For session tracking and audit logging. |
| Status | {{ $output.sendInteractive.status }} | Branch on "sent" vs "failed". |
| Error code | {{ $output.sendInteractive.errorCode }} | Log on the error port for diagnostics. |
Node Policies & GuardRails
| Policy Area | Recommendation |
| Credential storage | Store the access token in BizFirst Credentials Manager. Never inline in configuration. |
| Button limits | Maximum 3 buttons per button-type message. Plan your UX carefully — if you need more than 3 options, use the list type instead. List supports up to 10 sections with 10 rows each (100 total options). |
| Button ID design | Use descriptive, unique id values for buttons and list rows (e.g. confirm_address, not 1). The id is returned in the webhook payload and must be unambiguous for your workflow branching logic. |
| Title length | Button titles are truncated to 20 characters in WhatsApp. Keep titles concise and test on a real device before deploying. List row titles support up to 24 characters; descriptions up to 72. |
| Webhook handling | User responses arrive via a WhatsApp Webhook (inbound message event). Set up a WebhookTrigger node to receive responses and use the interactive.button_reply.id or interactive.list_reply.id to branch your workflow. |
| 24-hour window | Interactive messages are free-form and subject to the 24-hour customer service window. They cannot be sent as standalone messages outside this window — re-engage with a text template first. |
| JSON validation | Validate buttonsJson and sectionsJson in a Function node before passing to this operation. A single malformed character fails the entire message delivery. |
Examples
NPS Rating — Button Type
{
"resource": "message",
"operation": "sendInteractiveButtons",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"headerText": "How did we do?",
"bodyText": "We hope your experience with BizFirst was great! How would you rate your recent support interaction?",
"buttonsJson": "[{\"type\":\"reply\",\"reply\":{\"id\":\"nps_great\",\"title\":\"Great\"}},{\"type\":\"reply\",\"reply\":{\"id\":\"nps_okay\",\"title\":\"Okay\"}},{\"type\":\"reply\",\"reply\":{\"id\":\"nps_poor\",\"title\":\"Poor\"}}]",
"footerText": "Your feedback is anonymous"
}
A post-resolution workflow triggers this 10 minutes after a support case is closed. A WebhookTrigger listens for the reply; the selected button ID is matched in a Switch node that routes nps_poor to a human follow-up workflow.
Department Routing — List Type
{
"resource": "message",
"operation": "sendInteractiveList",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.customerPhone }}",
"bodyText": "Hello! How can we help you today? Please select the topic that best describes your enquiry.",
"buttonText": "Choose a topic",
"sectionsJson": "[{\"title\":\"Orders\",\"rows\":[{\"id\":\"topic_order_status\",\"title\":\"Track my order\",\"description\":\"Where is my order?\"},{\"id\":\"topic_order_cancel\",\"title\":\"Cancel an order\",\"description\":\"Request cancellation\"},{\"id\":\"topic_order_return\",\"title\":\"Return or refund\",\"description\":\"Initiate a return\"}]},{\"title\":\"Account\",\"rows\":[{\"id\":\"topic_account_login\",\"title\":\"Login issues\",\"description\":\"Password or access problems\"},{\"id\":\"topic_account_billing\",\"title\":\"Billing question\",\"description\":\"Invoices and payments\"}]}]",
"footerText": "Available Mon-Fri 9AM-6PM"
}
An inbound WhatsApp message triggers this triage workflow. The customer's list selection is received via webhook and used in a Switch node to route the conversation to the appropriate team queue.
Appointment Confirmation — Button Type
{
"resource": "message",
"operation": "sendInteractiveButtons",
"accessToken": "{{ $credentials.whatsAppToken }}",
"phoneNumberId": "123456789012345",
"to": "{{ $input.patientPhone }}",
"headerText": "Appointment Reminder",
"bodyText": "You have an appointment with {{ $input.doctorName }} tomorrow at {{ $input.time }}.\n\nWill you be attending?",
"buttonsJson": "[{\"type\":\"reply\",\"reply\":{\"id\":\"appt_confirm\",\"title\":\"Yes, I will attend\"}},{\"type\":\"reply\",\"reply\":{\"id\":\"appt_cancel\",\"title\":\"Cancel appointment\"}}]",
"footerText": "Cancel at least 2 hours before"
}
A ScheduledTrigger fires 24 hours before each appointment. Button reply appt_cancel triggers a cancellation sub-workflow that frees the slot and sends a rebooking link; appt_confirm sends a confirmation with directions.