WhatsApp Bot With Node.js and JavaScript: Cloud API, Baileys and whatsapp-web.js
By Erfan Ahmed, founder of Erfan MD · Updated October 2026
Node.js is the most common choice for building a WhatsApp bot in JavaScript. You have three main routes: the official WhatsApp Business Cloud API, and two unofficial libraries, whatsapp-web.js and Baileys. This guide explains how each one works, shows starter code for all three, compares their risks, and covers sessions, hosting and common errors, so you can pick the right tool before you write much code.
Three Ways to Build a WhatsApp Bot in JavaScript
| Option | How it connects | Official? | Resource use | Main catch |
|---|---|---|---|---|
| Cloud API + Express | Meta sends messages to your webhook | Yes | Very light | Meta setup, template rules and message charges |
| whatsapp-web.js | Runs WhatsApp Web in a headless browser (Puppeteer) | No | Heavy: needs a browser | Breaks when WhatsApp Web changes; ban risk |
| Baileys | Talks to WhatsApp over a WebSocket as a linked device | No | Light | API changes between versions; ban risk |
Baileys is the library behind many community "MD" bots, which is why you see searches like "WhatsApp bot Baileys GitHub". Both unofficial libraries state in their own documentation that WhatsApp does not allow bots or custom clients and that blocking is possible. Take that warning seriously.
Before You Start
- Node.js LTS (version 18 or newer is a safe baseline) and
npm. - A spare WhatsApp number for any unofficial library. Do not test on your main number.
- For the Cloud API: a Meta developer app, phone number ID, access token and a public HTTPS URL. Our guide to creating a WhatsApp bot explains the Meta side.
Option 1: Cloud API With Express (Recommended for Business)
npm init -y
npm install express dotenvCreate index.js. It verifies the webhook, checks Meta's signature, and replies to text messages with Node's built-in fetch:
require('dotenv').config();
const crypto = require('crypto');
const express = require('express');
const app = express();
const { VERIFY_TOKEN, WA_TOKEN, WA_PHONE_ID, APP_SECRET } = process.env;
const GRAPH = 'v21.0'; // use the current version from Meta's docs
// keep the raw body so the signature can be checked
app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf; } }));
const MENU = 'Hi! Reply 1 for hours, 2 for prices, 0 for a person.';
const REPLIES = {
'1': 'We are open Monday to Saturday, 9am to 6pm.',
'2': 'Our prices are at example.com/prices.',
'0': 'A team member will reply soon.',
};
async function sendText(to, body) {
const res = await fetch(`https://graph.facebook.com/${GRAPH}/${WA_PHONE_ID}/messages`, {
method: 'POST',
headers: { Authorization: `Bearer ${WA_TOKEN}`, 'Content-Type': 'application/json' },
body: JSON.stringify({ messaging_product: 'whatsapp', to, type: 'text', text: { body } }),
});
if (!res.ok) console.error('Send failed:', await res.text());
}
app.get('/webhook', (req, res) => {
if (req.query['hub.mode'] === 'subscribe' && req.query['hub.verify_token'] === VERIFY_TOKEN) {
return res.status(200).send(req.query['hub.challenge']);
}
res.sendStatus(403);
});
app.post('/webhook', async (req, res) => {
const expected = 'sha256=' + crypto.createHmac('sha256', APP_SECRET).update(req.rawBody).digest('hex');
const sig = req.get('X-Hub-Signature-256') || '';
if (sig.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
return res.sendStatus(403);
}
res.sendStatus(200); // answer fast, then work
const msg = req.body?.entry?.[0]?.changes?.[0]?.value?.messages?.[0];
if (!msg) return;
if (msg.type === 'text') {
const text = msg.text.body.trim().toLowerCase();
await sendText(msg.from, REPLIES[text] || MENU);
} else {
await sendText(msg.from, 'I can only read text for now. ' + MENU);
}
});
app.listen(3000, () => console.log('Bot listening on port 3000'));Use a tunnel such as ngrok to test locally, set the webhook URL and verify token in the Meta dashboard, and subscribe to the messages field. The logic matches our Python version, so you can compare the two.
Option 2: whatsapp-web.js
This library launches a headless Chrome browser, loads WhatsApp Web and listens to events. Setup is quick, which is why it is popular for experiments:
npm install whatsapp-web.js qrcode-terminal
const { Client, LocalAuth } = require('whatsapp-web.js');
const qrcode = require('qrcode-terminal');
const client = new Client({ authStrategy: new LocalAuth() });
client.on('qr', (qr) => qrcode.generate(qr, { small: true }));
client.on('ready', () => console.log('Bot is ready'));
client.on('message', async (msg) => {
if (msg.body.toLowerCase() === 'ping') {
await msg.reply('pong');
}
});
client.initialize();Scan the QR code from Linked Devices on your spare phone. LocalAuth stores the session on disk so you do not scan every time. The cost is memory, since a full browser runs in the background, which makes it a poor fit for small free hosting plans.
Option 3: Baileys
Baileys connects directly over WebSocket without a browser, so it is lighter and faster. It is also a moving target, because the maintainers change its API between versions and newer releases may need ES module imports. Always follow the README for the version you install. A typical connection looks like this:
npm install @whiskeysockets/baileys pino
const {
default: makeWASocket,
useMultiFileAuthState,
DisconnectReason,
} = require('@whiskeysockets/baileys');
async function start() {
const { state, saveCreds } = await useMultiFileAuthState('auth_session');
const sock = makeWASocket({ auth: state });
sock.ev.on('creds.update', saveCreds);
sock.ev.on('connection.update', ({ connection, lastDisconnect }) => {
if (connection === 'close') {
const code = lastDisconnect?.error?.output?.statusCode;
if (code !== DisconnectReason.loggedOut) start(); // reconnect
else console.log('Logged out: delete auth_session and pair again');
} else if (connection === 'open') {
console.log('Connected');
}
});
sock.ev.on('messages.upsert', async ({ messages }) => {
const m = messages[0];
if (!m.message || m.key.fromMe) return;
const text = m.message.conversation || m.message.extendedTextMessage?.text || '';
if (text.toLowerCase() === 'ping') {
await sock.sendMessage(m.key.remoteJid, { text: 'pong' });
}
});
}
start();The auth_session folder holds your login. Treat it like a password: keep it out of GitHub, back it up, and never share it. If a project asks you to upload a session file to a stranger's server, that person can control your WhatsApp account.
Pairing code instead of QR
Many bots let you link with a pairing code rather than a QR scan. You enter your number with the country code, receive an 8-character code, and type it under Linked Devices > Link a Device > Link with phone number instead. Baileys exposes a method for requesting this code, but its exact usage changes by version, so check the README. Erfan MD uses the same pairing-code flow through its own server page.
Low-Code Option: Node-RED
Node-RED is a visual flow tool built on Node.js. Many people search "WhatsApp bot Node-RED" because it needs little code. With the official API, a flow can use an HTTP In node as the webhook, a Function or Switch node to choose a reply, and an HTTP Request node to call the messages endpoint. Community nodes for WhatsApp exist too, but check who maintains them and how they connect before trusting them with your number.
Project Structure That Scales
Once your bot has more than a few replies, split the code so each feature is easy to find:
index.jsfor the connection and webhook only.commands/with one small file per command, each exporting a name and a handler function.lib/for helpers such as sending messages, logging and rate limiting..envand.gitignoreto keep secrets and sessions out of version control.
A simple command loader reads the commands folder at start-up and calls the handler whose name matches the first word of the message after your prefix. This is how most open-source bots handle thousands of commands.
Hosting a Node.js WhatsApp Bot
- Cloud API bots only need to answer web requests, so a small server, a Node-friendly platform or a serverless host works.
- Baileys bots need an always-on server and a session folder that survives restarts.
- whatsapp-web.js bots need extra memory for the browser.
Run any long-lived bot under PM2 (pm2 start index.js --name wa-bot) or Docker so it restarts after a crash. Our deployment guide has the full commands.
Finding Node.js Bot Code on GitHub
GitHub has hundreds of JavaScript WhatsApp bots. Before you run one, check what connection it uses, when it was last updated, what its dependencies are, and where it sends data. Read the code that touches your session or tokens, and be wary of obfuscated files or install scripts that download unknown code. Forks with the same code and new branding are common, so look at the original project.
Common Problems and Fixes
| Problem | Likely cause | Fix |
|---|---|---|
| QR code or pairing fails | Old linked device or wrong number format | Remove old linked devices and try again with the country code |
| Bot logs out after restart | Session folder not saved | Keep the auth folder on a persistent disk or volume |
| Frequent disconnects | Sleeping free host or two sessions on one number | Use an always-on server and one session per number |
| whatsapp-web.js crashes | Browser missing dependencies or low memory | Install Chrome libraries and use a larger server |
| Library stops working after an update | WhatsApp changed its protocol | Update the library or pin a known working version |
| Cloud API webhook returns 403 | Verify token or signature mismatch | Match the token and use the raw body for the signature |
Risks You Should Understand
- Do not bulk message strangers. It is the fastest way to get banned.
- Protect the session and tokens. Anyone with them can act as your number.
- Tell people it is a bot, and honor "stop" requests.
Related guides: WhatsApp bot with Python, WhatsApp bot GitHub guide, pair code guide and API pricing.
Frequently Asked Questions
What is the best library for a WhatsApp bot in Node.js?
For business, the official Cloud API with Express. For personal or group projects, Baileys is lighter than whatsapp-web.js, but both are unofficial and risky.
Is Baileys safe to use?
The library is not malware, but it is unofficial and accounts using it can be banned. The bigger danger is untrusted bots built on it that steal sessions, so read any code you run.
Can I run a Node.js WhatsApp bot for free?
You can build and test for free. Free hosting often sleeps or has limits, so it is unreliable for a bot that must stay connected.
Which is lighter, Baileys or whatsapp-web.js?
Baileys, because it does not run a browser. whatsapp-web.js needs far more memory.
Do I need to code to make a WhatsApp bot?
No. Tools like n8n, Node-RED or no-code platforms let you build a basic bot with blocks.
Want a ready bot without the coding?
Erfan MD links to your WhatsApp with a pairing code and handles replies, broadcasts and follow-ups for you.
Explore Erfan MD