This repository shows how to send every currently known WhatsApp interactive / native flow button type using WhiskeySockets (Baileys fork) without modifying core source. The functionality is packaged and published as the npm package baileys_helper which reproduces the binary node structure the official client emits so buttons render correctly for both private & group chats.
By default, WhiskeySockets cannot send interactive buttons while itsukichan can. The root cause is that WhiskeySockets lacks the required binary node wrappers (biz, interactive, native_flow) that WhatsApp expects for interactive messages.
The enhanced functionality provided by the baileys_helper package provides the missing functionality by:
- Detecting button messages using the same logic as itsukichan
- Converting WhiskeySockets'
interactiveButtonsformat to the proper protobuf structure - Adding missing binary nodes (
biz,interactive,native_flow,bot) viaadditionalNodes - Automatically handling private vs group chat requirements
- ✅ No modifications to WhiskeySockets or itsukichan folders
- ✅ Template functionality removed as requested
- ✅ Automatic binary node injection for button messages
- ✅ Private chat support (adds
botnode withbiz_bot: '1') - ✅ Group chat support (adds only
biznode) - ✅ Backward compatibility (regular messages pass through unchanged)
const { sendButtons } = require('baileys_helper');
await sendButtons(sock, jid, {
title: 'Header Title', // optional header
text: 'Pick one option below', // body
footer: 'Footer text', // optional footer
buttons: [
{ id: 'quick_1', text: 'Quick Reply' }, // legacy simple shape auto‑converted
{
name: 'cta_url',
buttonParamsJson: JSON.stringify({
display_text: 'Open Site',
url: 'https://example.com'
})
}
]
});For full control (multiple advanced button kinds in one message) use sendInteractiveMessage with interactiveButtons directly.
const { sendInteractiveMessage } = require('baileys_helper');
await sendInteractiveMessage(sock, jid, {
text: 'Advanced native flow demo',
footer: 'All the things',
interactiveButtons: [
// Quick reply (explicit form)
{
name: 'quick_reply',
buttonParamsJson: JSON.stringify({ display_text: 'Reply A', id: 'reply_a' })
},
// Single select picker (list inside a button)
{
name: 'single_select',
buttonParamsJson: JSON.stringify({
title: 'Pick One',
sections: [{
title: 'Choices',
rows: [
{ header: 'H', title: 'Hello', description: 'Says hi', id: 'opt_hello' },
{ header: 'B', title: 'Bye', description: 'Says bye', id: 'opt_bye' }
]
}]
})
}
]
});Below are the most common & observed name values for nativeFlowMessage.buttons[] along with their required JSON keys. You can mix several in one interactiveButtons array (WhatsApp will decide layout).
| Name | Purpose | buttonParamsJson (required keys) |
|---|---|---|
quick_reply |
Simple reply that sends its id back |
{ display_text, id } |
single_select |
In‑button picker list | { title, sections:[{ title?, rows:[{ id, title, description?, header? }] }] } |
cta_url |
Open URL | { display_text, url, merchant_url? } |
cta_copy |
Copy text to clipboard | { display_text, copy_code } |
cta_call |
Tap to dial | { display_text, phone_number } |
cta_catalog |
Open business catalog | { display_text? } (WA may ignore extra keys) |
send_location |
Request user location (special flow) | { display_text? } |
review_and_pay |
Order / payment summary (special) | Payment structured payload (server‑validated) |
payment_info |
Payment info flow | Payment structured payload |
mpm |
Multi product message (catalog) | Vendor internal structure |
wa_payment_transaction_details |
Show transaction | Transaction reference keys |
automated_greeting_message_view_catalog |
Greeting -> catalog | (Minimal / internal) |
Not all special names are guaranteed to render outside official / business clients; unsupported ones are simply ignored by WhatsApp. Core stable ones for bots are: quick_reply, single_select, cta_url, cta_copy, cta_call.
await sendInteractiveMessage(sock, jid, {
text: 'Contact actions',
interactiveButtons: [
{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Docs', url: 'https://example.com' }) },
{ name: 'cta_copy', buttonParamsJson: JSON.stringify({ display_text: 'Copy Code', copy_code: 'ABC-123' }) },
{ name: 'cta_call', buttonParamsJson: JSON.stringify({ display_text: 'Call Support', phone_number: '+1234567890' }) }
]
});await sendInteractiveMessage(sock, jid, {
text: 'Explore products or reply',
interactiveButtons: [
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hello', id: 'hi' }) },
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Pricing', id: 'pricing' }) },
{ name: 'cta_catalog', buttonParamsJson: JSON.stringify({}) }
]
});await sendInteractiveMessage(sock, jid, {
text: 'Please share your location',
interactiveButtons: [
{ name: 'send_location', buttonParamsJson: JSON.stringify({ display_text: 'Share Location' }) }
]
});await sendInteractiveMessage(sock, jid, {
text: 'Choose one item',
interactiveButtons: [
{ name: 'single_select', buttonParamsJson: JSON.stringify({
title: 'Menu',
sections: [{
title: 'Main',
rows: [
{ id: 'it_1', title: 'First', description: 'First choice' },
{ id: 'it_2', title: 'Second', description: 'Second choice' }
]
}]
}) }
]
});Tip: Legacy simple objects like
{ id: 'x', text: 'Label' }passed tosendButtonsauto‑convert toquick_reply.
Private chat: adds biz + interactive/native_flow + bot (biz_bot=1).
Group chat: adds only biz + interactive/native_flow.
When special first button names (review_and_pay, payment_info, mpm, etc.) are detected, version/name attributes change to match official client traffic so WhatsApp enables those flows.
The wrapper detects button types using the same logic as itsukichan:
listMessage→ 'list'buttonsMessage→ 'buttons'interactiveMessage.nativeFlowMessage→ 'native_flow'
Authoring (you write):
{ text, footer, interactiveButtons: [{ name, buttonParamsJson }, ...] }Wrapper builds (sent to WA):
{ interactiveMessage: { nativeFlowMessage: { buttons: [...] }, body:{ text }, footer:{ text } } }Low‑level power helper used by all higher level wrappers. Use this when you need to:
- Mix several advanced button kinds in one message (e.g.
quick_reply+single_select+cta_url). - Provide pre‑built
interactiveMessagecontent (after internal transformation) while still benefiting from automatic binary node injection. - Attach custom relay options (
statusJidList,additionalAttributes, experimental fields) or manually append extraadditionalNodes.
async function sendInteractiveMessage(sock, jid, content, options = {})sock: Active WhiskeySockets/Baileys socket (must exposerelayMessage,logger,authStateoruser).jid: Destination WhatsApp JID (user or group). Auto‑detects group viaWABinary.isJidGroup.content: High‑level authoring object. Accepts either a regular Baileys message shape or the enhanced authoring shape:text(string) Body text (mapped tointeractiveMessage.body.text).footer(string) Footer (mapped tointeractiveMessage.footer.text).title/subtitle(string) Optional header title (mapped tointeractiveMessage.header.title).interactiveButtons(Array) Array of button descriptors. Each item should be either:{ name: '<native_flow_name>', buttonParamsJson: JSON.stringify({...}) }(already normalized), or- A legacy quick reply shape
{ id, text }/{ buttonId, buttonText: { displayText } }which is auto‑normalized to aquick_reply.
- Any other Baileys message keys (e.g.
contextInfo) pass through unchanged.
options: (Optional) Extra relay + generation options:- All fields accepted by
generateWAMessageFromContent(e.g. customtimestamp). additionalNodes(Array) Prepend your own binary nodes (the function appends required interactive nodes after detection).additionalAttributes(Object) Extra attributes for the root relay stanza.statusJidList,useCachedGroupMetadata(advanced Baileys relay options).
- All fields accepted by
- Calls
convertToInteractiveMessage(content)ifinteractiveButtonsexist, producing:{ interactiveMessage: { nativeFlowMessage: { buttons: [...] }, header?, body?, footer? } }
- Imports WhiskeySockets internal helpers (
generateWAMessageFromContent,normalizeMessageContent,isJidGroup,generateMessageIDV2). Throws if unavailable. - Builds a raw
WAMessagebypassing normal send validation (lets unsupported interactive types through). - Normalizes and determines button type via
getButtonTypethen derives binary node tree withgetButtonArgs. - Injects required binary nodes:
- Always a
biznode (with nestedinteractive/native_flow/...for buttons and lists) when interactive. - Adds
{ tag: 'bot', attrs: { biz_bot: '1' } }automatically for private (1:1) chats enabling rendering of interactive flows.
- Always a
- Relays the message using
relayMessagewithadditionalNodes. - Optionally emits the message locally (
sock.upsertMessage) for private chats ifsock.config.emitOwnEventsis set (groups are skipped to avoid duplicates).
Resolves with the full constructed WAMessage object ({ key, message, messageTimestamp, ... }) so you can log/store/await acks exactly like a standard sock.sendMessage call.
- Throws
Socket is requiredifsockis null/undefined. - Throws
WhiskeySockets functions not availableif internal modules cannot be loaded (e.g. path changes). In such a case you may fall back to plainsock.sendMessagefor non‑interactive messages.
- Use
sendButtons/sendInteractiveButtonsBasicfor simple quick replies + common CTA cases. - Use
sendInteractiveMessagefor any combination includingsingle_select, special native flow names, or when you need to attach custom nodes.
const { sendInteractiveMessage } = require('baileys_helper');
await sendInteractiveMessage(sock, jid, {
text: 'Pick or explore',
footer: 'Advanced demo',
interactiveButtons: [
{ name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hi', id: 'hi' }) },
{ name: 'cta_url', buttonParamsJson: JSON.stringify({ display_text: 'Docs', url: 'https://example.com' }) },
{ name: 'single_select', buttonParamsJson: JSON.stringify({
title: 'Menu',
sections: [{
title: 'Options',
rows: [
{ id: 'a', title: 'Alpha', description: 'First item' },
{ id: 'b', title: 'Beta', description: 'Second item' }
]
}]
}) }
]
}, {
additionalNodes: [ { tag: 'biz', attrs: { experimental_flag: '1' } } ] // will be merged before auto interactive nodes
});| First Button Name | Injected Node Variant | Notes |
|---|---|---|
review_and_pay |
biz with native_flow_name=order_details |
Payment/order style flow |
payment_info |
biz with native_flow_name=payment_info |
Payment info flow |
mpm, cta_catalog, send_location, call_permission_request, wa_payment_transaction_details, automated_greeting_message_view_catalog |
biz > interactive(native_flow v=1) > native_flow(v=2,name=<name>) |
Specialized (may require official client) |
| Anything else / mixed | biz > interactive(native_flow v=1) > native_flow(v=9,name=mixed) |
Generic path covering standard quick replies, lists, CTAs |
Cost is roughly equivalent to a standard sendMessage call; extra overhead is a small synchronous transformation + node injection. Suitable for high‑volume bots. Consider standard Baileys concurrency limits for large broadcast scenarios.
- Temporary console log emitted:
Interactive send: { type, nodes, private }– remove or redirect if noisy. - If buttons do not render: ensure first binary node injected is
bizand private chats include thebotnode. - Confirm each button's
buttonParamsJsonis valid JSON string (catch JSON.stringify mistakes early).
- Forgetting to JSON.stringify
buttonParamsJsonpayloads. - Using
sendInteractiveMessagewithout a socket that includesrelayMessage(e.g., passing a partially constructed object). - Adding your own
botnode for private chats (not needed; auto added). - Expecting unsupported special flows (payments/catalog) to render in a non‑business account—WhatsApp may silently ignore them.
If you already built a correct interactiveMessage object you can call:
await sendInteractiveMessage(sock, jid, {
interactiveMessage: {
nativeFlowMessage: {
buttons: [ { name: 'quick_reply', buttonParamsJson: JSON.stringify({ display_text: 'Hi', id: 'hi' }) } ]
},
body: { text: 'Direct native flow' }
}
});The helper will still inject binary nodes & bot node as required.
helpers/buttons.js- Enhanced with binary node support (template functionality removed)export.js- Central export surface for the package and metadata helper
- ✅ WhiskeySockets 7.0.0-rc.2+
- ✅ Node.js 20+
- ✅ All button types supported by itsukichan
- ✅ Private and group chats
You can now send all mainstream interactive button variants (quick replies, URL / copy / call CTAs, single select lists) plus experimental special flows from WhiskeySockets exactly like the official client, with automatic handling for groups vs private chats and without editing fork source.