Interactive Buttons & Rich Response

WhatsApp supports multiple interactive button types: native buttons, template buttons, list select, and AI-style rich response markdown.

Native Buttons

Native WA buttons appear below your message. Supports header image, title, body text, and footer. Maximum 3 buttons.

FieldTypeDescription
textstringBody text (minimum a space if not used)
buttonsArrayArray of button objects. Max 3
image / videoWAMediaUploadOptional media header
titlestringText header (used when no media)
footerstringFooter text

Button Structure

FieldDescription
buttonIdUnique button ID for detection
buttonText.displayTextButton label text
type1 = reply (standard)
await sock.sendMessage(jid, {
  text: 'Choose an option:',
  title: 'Main Menu',
  footer: 'Select one',
  buttons: [
    { buttonId: 'products', buttonText: { displayText: 'Products' }, type: 1 },
    { buttonId: 'help', buttonText: { displayText: 'Help' }, type: 1 },
    { buttonId: 'about', buttonText: { displayText: 'About' }, type: 1 },
  ],
})

// Button with image header
await sock.sendMessage(jid, {
  text: 'Special promotion!',
  image: { url: './banner.jpg' },
  buttons: [
    { buttonId: 'buy', buttonText: { displayText: 'Buy Now' }, type: 1 },
  ],
})

Handle Button Clicks

When a user clicks a button, WA sends a buttonsResponseMessage. Listen on messages.upsert:

sock.ev.on('messages.upsert', ({ messages }) => {
  for (const msg of messages) {
    const btn = msg.message?.buttonsResponseMessage
    if (btn) {
      console.log('User selected:', btn.selectedButtonId, btn.selectedDisplayText)
    }
  }
})

Template Buttons

Three types: URL (opens in browser), Quick Reply (sends reply), Call (dials number).

Content Structure

FieldDescription
textMain body text
templateButtonsArray of buttons. Can mix all three types
footerFooter text (optional)

Button Types

TypeStructureDescription
URL{ urlButton: { displayText, url } }Opens URL in browser with confirmation
Quick Reply{ quickReplyButton: { displayText, id } }Sends automatic reply with ID
Call{ callButton: { displayText, phoneNumber } }Dials phone number
await sock.sendMessage(jid, {
  text: 'Need help?',
  templateButtons: [
    { urlButton: { displayText: 'Visit Site', url: 'https://example.com' } },
    { quickReplyButton: { displayText: 'Yes please', id: 'need_help' } },
    { callButton: { displayText: 'Call Support', phoneNumber: '+6281234567890' } },
  ],
})

Handle Quick Reply Button

Quick Reply responses come through the same buttonsResponseMessage as native buttons:

sock.ev.on('messages.upsert', ({ messages }) => {
  for (const msg of messages) {
    if (msg.message?.buttonsResponseMessage) {
      const { selectedButtonId, selectedDisplayText } = msg.message.buttonsResponseMessage
      console.log(`User clicked "${selectedDisplayText}" (${selectedButtonId})`)
    }
  }
})

List Select

Dropdown menu with sections and rows. Ideal for multi-level menus or catalogs.

FieldTypeDescription
textstringMain body text
buttonTextstringDropdown button label
titlestringTitle (optional)
footerstringFooter (optional)
sectionsSection[]Array of categories with title & rows

Row Structure

FieldDescription
idUnique row ID for detection
titleRow title
descriptionRow description (optional)
await sock.sendMessage(jid, {
  text: 'Select menu:',
  buttonText: 'View Menu',
  title: 'Menu Items',
  footer: 'Select one',
  sections: [
    {
      title: 'Drinks',
      rows: [
        { id: 'coffee', title: 'Coffee', description:  'Black coffee' },
        { id: 'tea', title: 'Tea', description: 'Sweet tea' },
        { id: 'juice', title: 'Orange Juice', description: 'Fresh squeezed' },
      ],
    },
    {
      title: 'Food',
      rows: [
        { id: 'rice', title: 'Fried Rice', description: 'Special fried rice' },
        { id: 'noodles', title: 'Chicken Noodles', description:  'Complete chicken noodles' },
      ],
    },
  ],
})

Handle List Selection

sock.ev.on('messages.upsert', ({ messages }) => {
  for (const msg of messages) {
    const list = msg.message?.listResponseMessage
    if (list) {
      const selectedId = list.singleSelectReply?.selectedRowId
      const selectedTitle = list.singleSelectReply?.selectedRowTitle
      console.log(`Selected: ${selectedTitle} (${selectedId})`)
    }
  }
})

Interactive Response (Native Flow)

All button types above (native, template, list select) are internally converted to native_flow messages by WhatsApp. You can handle responses via buttonsResponseMessage, listResponseMessage, or interactiveResponseMessage.

sock.ev.on('messages.upsert', ({ messages }) => {
  for (const msg of messages) {
    const interactive = msg.message?.interactiveResponseMessage
    if (interactive) {
      const { nativeFlowResponseMessage } = interactive
      console.log('Response:', nativeFlowResponseMessage)
    }
  }
})

Rich Response (AI Markdown)

Converts markdown into an interactive AI-style message (similar to ChatGPT on WhatsApp). Add rich: true and write markdown in text.

Supported Markdown Syntax

FeatureSyntaxResult
Heading# Title to ####Bold header text
Bold*text*Bold
Italic_text_Italic
Strikethrough~text~Strikethrough
Code Block```javascript ... ```Syntax-highlighted code
Table| A | B | with separatorFormatted table
Image![alt](url)Inline image
Blockquote> textQuoted text
Horizontal Rule--- / *** / ___Divider
Suggest Prompts:::suggest ... :::Suggested action pills
const md = [
  '# Bot Dashboard',
  '',
  'Status: *Online* _24/7_ ~maintenance~',
  '',
  '```javascript',
  "console.log('Ready')",
  '```',
  '',
  '| Metric | Value |',
  '|--------|-------|',
  '| Users  | 1.234 |',
  '',
  '![Chart](https://picsum.photos/400/200)',
  '',
  ':::suggest',
  'Check Status | Restart | Help',
  ':::',
].join('\n')

await sock.sendMessage(jid, { text: md, rich: true })

Note: Rich response only works with text + rich: true. Cannot be combined with media or buttons. Useful for AI bots and interactive dashboards.

Request Phone Number

Ask user to share their phone number via a native button. When tapped, WA sends the user's number back to the bot.

await sock.sendMessage(jid, { requestPhoneNumber: true })

Handle Phone Number Response

sock.ev.on('messages.upsert', ({ messages }) => {
  for (const msg of messages) {
    if (msg.message?.requestPhoneNumberMessage) {
      console.log('User phone:', msg.key.participant || msg.key.remoteJid)
    }
  }
})
← Back Next: Events →