How to Build a WhatsApp Bot in 2026 (Step-by-Step)
A practical end-to-end tutorial for building a WhatsApp bot in 2026 — compare the official Cloud API against Baileys and whatsapp-web.js, then ship a working bot with real code, webhooks, media handling and honest compliance guidance.
- There are three realistic paths in 2026: Meta's official WhatsApp Cloud API (sanctioned, free dev sandbox, per-message pricing in production) and two unofficial libraries — Baileys (WebSocket, no browser) and whatsapp-web.js (Puppeteer + Chromium).
- Only the Cloud API carries zero Terms-of-Service ban risk. Baileys and whatsapp-web.js are reverse-engineered, ship their own disclaimers, and have documented account bans — user reports describe bans sometimes after only a handful of messages.
- For the official path you build a webhook: a GET handler that echoes hub.challenge, and a POST handler that verifies the X-Hub-Signature-256 HMAC with crypto.timingSafeEqual before reading messages.
- The 24-hour customer service window lets you send free-form (and free) replies for 24h after each user message; outside it you must use a pre-approved template.
- Use the Cloud API for anything production or commercial; reach for Baileys or whatsapp-web.js only for prototypes or personal projects where the ban risk is acceptable.
Three paths to a WhatsApp bot — pick before you code
Before writing a single line, decide which platform you are building on, because the choice dictates everything else: your code, your hosting, your costs, and whether your number can be banned. In 2026 there are three realistic ways to put a WhatsApp bot into the world.
The first is the official one — Meta's WhatsApp Cloud API, a hosted Graph API endpoint at graph.facebook.com. It is the only WhatsApp-sanctioned way to build a bot, it gives you a free test number and sandbox to develop against, and it carries no Terms-of-Service ban risk. The other two are unofficial, reverse-engineered libraries that automate a regular WhatsApp account: Baileys, a pure WebSocket client with no browser, and whatsapp-web.js, which drives WhatsApp Web inside a headless Chromium. Both are powerful and free, both let you automate an ordinary number, and both put that number at risk.
This guide compares all three honestly, then walks you through a concrete build on each side: an official Cloud API webhook bot, and a quick unofficial bot with Baileys. By the end you will know which path fits your project and have working code to start from.
Cloud API vs Baileys vs whatsapp-web.js
Here is the decision at a glance. Versions are a snapshot verified mid-2026 — check each project's releases page when you read this, since the unofficial libraries move quickly.
| WhatsApp Cloud API | Baileys | whatsapp-web.js | |
|---|---|---|---|
| Owner | Meta (official) | WhiskeySockets | wwebjs (orig. Pedro S. Lopez) |
| Install | REST / Graph API | npm i baileys | npm i whatsapp-web.js |
| Architecture | Meta-hosted HTTP endpoint | WebSocket multi-device, no browser | Puppeteer + Chromium (WhatsApp Web) |
| License | Hosted service (Meta ToS) | MIT | Apache-2.0 |
| Latest | per-message pricing model | 7.0.0 line (rc13) | v1.34.7 (Apr 2026) |
| Auth | Access token + verified number | QR or pairing code | QR scan |
| Footprint | None (you call an API) | Lightweight (Node 17+, ESM) | Heavy (full Chromium per session) |
| Ban risk | None — sanctioned | Real — documented bans | Real — documented bans |
| Best for | Production, commercial, scale | Lightweight prototypes, personal | Web-feature parity, don't mind Chromium |
Path A — Official Cloud API: setup and sandbox
Start here if you are building anything production or commercial. The Cloud API is compliant, scalable, and free to develop against. The setup, all in the Meta dashboard at developers.facebook.com, is roughly: create a Meta app, add the WhatsApp product, and you are issued a free test phone number attached to a test WhatsApp Business Account (a sandbox). You can send and receive messages with that test number at no charge while you develop — no message billing until you move to a real number.
From the dashboard, grab three values you will use in code: a temporary access token, your test number's phone number ID, and your app secret (used to verify incoming webhooks). Going to production later requires a real number, Meta Business verification, and usually a Business Solution Provider — but none of that is needed to build and test the bot.
- Create a Meta app at developers.facebook.com and add the WhatsApp product.
- Copy the test phone number ID and the temporary access token from the API Setup panel.
- Add a recipient test number (your own phone) so the sandbox can message you.
- Note your App Secret under App Settings → Basic — you will need it to verify webhook signatures.
Path A — Receiving messages with a verified webhook
The Cloud API delivers incoming messages by calling a webhook URL you host. Setting it up has two halves. First, when you register the URL, Meta sends a GET request to verify you own it. It includes three query params: hub.mode (which will be 'subscribe'), hub.verify_token (a secret string you chose), and hub.challenge (a random value). You check the token matches, then echo hub.challenge back as plain text with a 200. Your endpoint must be HTTPS with a valid, non-self-signed TLS certificate.
import express from 'express'
import crypto from 'crypto'
const app = express()
const VERIFY_TOKEN = process.env.VERIFY_TOKEN
const APP_SECRET = process.env.APP_SECRET
// Keep the raw body so we can verify the signature later.
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf } }))
// 1) Webhook verification (GET): echo hub.challenge back.
app.get('/webhook', (req, res) => {
const mode = req.query['hub.mode']
const token = req.query['hub.verify_token']
const challenge = req.query['hub.challenge']
if (mode === 'subscribe' && token === VERIFY_TOKEN) {
return res.status(200).send(challenge)
}
return res.sendStatus(403)
})Second, real messages arrive as POST requests to the same URL. Meta signs every payload with an HMAC-SHA256 of the raw request body, keyed by your App Secret, in the header X-Hub-Signature-256: sha256=.... You must verify this before trusting the payload — and you must compare it with a timing-safe function (crypto.timingSafeEqual), never a plain === string comparison, which is vulnerable to timing attacks.
// 2) Incoming messages (POST): verify HMAC, then read the message.
app.post('/webhook', (req, res) => {
const signature = req.get('X-Hub-Signature-256') || ''
const expected = 'sha256=' + crypto
.createHmac('sha256', APP_SECRET)
.update(req.rawBody)
.digest('hex')
const a = Buffer.from(signature)
const b = Buffer.from(expected)
if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) {
return res.sendStatus(401)
}
const entry = req.body.entry?.[0]?.changes?.[0]?.value
const msg = entry?.messages?.[0]
if (msg) {
console.log('From', msg.from, 'type', msg.type, 'text', msg.text?.body)
}
res.sendStatus(200) // Always 200 quickly so Meta doesn't retry.
})
app.listen(3000)Path B — A quick bot with Baileys (unofficial)
If you need a throwaway prototype or a personal-project bot and accept the ban risk, Baileys is the lightest unofficial option. It speaks WhatsApp's multi-device WebSocket protocol directly — no browser, no Puppeteer — so it starts fast and sips RAM. Install the package as baileys (the old @whiskeysockets/baileys scope is superseded), and note that v7 requires Node 17+ and is published as ESM.
Authentication is by QR code or pairing code, and you persist the session with useMultiFileAuthState so you don't re-scan on every restart. The main entry point is makeWASocket. Here is a minimal echo-style bot that connects, prints a QR, and replies to incoming text:
import makeWASocket, { useMultiFileAuthState } from 'baileys'
import qrcode from 'qrcode-terminal'
const { state, saveCreds } = await useMultiFileAuthState('auth')
const sock = makeWASocket({ auth: state })
sock.ev.on('creds.update', saveCreds)
sock.ev.on('connection.update', ({ qr }) => {
if (qr) qrcode.generate(qr, { small: true }) // scan from WhatsApp > Linked devices
})
sock.ev.on('messages.upsert', async ({ messages }) => {
const msg = messages[0]
if (!msg.message || msg.key.fromMe) return
const text = msg.message.conversation || msg.message.extendedTextMessage?.text
await sock.sendMessage(msg.key.remoteJid!, { text: `You said: ${text ?? ''}` })
})whatsapp-web.js follows the same shape conceptually — you create a Client, listen for a 'qr' event, then a 'message' event, and call message.reply(). The difference is footprint: it boots a full Chromium via Puppeteer per session, so it is heavier but tracks the live web UI closely and tends to support new web features sooner. Choose it when you want that web-client parity and don't mind the browser overhead; choose Baileys when you want minimal resources.
Deploying your bot
The two paths deploy very differently. A Cloud API bot is just a stateless web service — your webhook handler — so it deploys like any HTTP app: a container or serverless function behind HTTPS on a platform such as Railway, Render, Fly.io, or a VPS. There is no session to keep alive; Meta holds the WhatsApp connection. Set your env vars (access token, app secret, verify token, phone number ID), point the webhook at your public HTTPS domain, and you are done.
An unofficial bot is stateful — it holds a live WhatsApp connection and an auth session that must survive restarts. That rules out most serverless platforms. Run it on a persistent host (a small VPS or a long-running container), and mount the auth folder on durable storage so a redeploy doesn't force a fresh QR scan. For whatsapp-web.js you also need the Chromium dependencies present in the image, which is why its Docker images are larger.
- Cloud API: stateless web service, deploy anywhere with HTTPS, no session to persist.
- Baileys: persistent host, durable volume for the auth/ folder, lightweight (no browser).
- whatsapp-web.js: persistent host + Chromium in the image; size the container for a full browser.
- Never commit tokens, app secrets, or the auth session to git — use environment variables and secret storage.
Compliance and ban risk — read before you ship
Be honest with yourself about which platform you chose. Baileys and whatsapp-web.js are both unofficial and reverse-engineered, and both say so. whatsapp-web.js's README states plainly that 'WhatsApp does not allow bots or unofficial clients on their platform, so this shouldn't be considered totally safe.' The Baileys maintainers say they do not condone practices that violate the Terms of Service and discourage stalkerware, bulk, or automated messaging. These are not theoretical warnings.
Real bans are documented. In the project's own issue tracker, users report the 'Your account has been disabled because you are using an unofficial WhatsApp' block (see the linked whatsapp-web.js ban thread in Sources). Those user reports describe bans landing after widely varying volumes — sometimes after only a handful of messages — though the exact threshold is anecdotal, not an officially published number. An unofficial bot puts your personal or business number at risk of permanent ban, and a banned number is hard to recover. If the number matters to you or your business, that risk should weigh heavily.
The Cloud API, by contrast, has no ToS ban risk because it is the sanctioned channel — at the cost of per-message pricing and stricter messaging rules: the 24-hour window, template approval, and tiered messaging limits that begin around 250 business-initiated conversations to unique customers per 24 hours and scale up with quality and verification. Note that since October 7, 2025 these messaging limits apply per Business Portfolio, not per individual phone number, so adding more numbers under the same portfolio does not stack the cap. Match the platform to the stakes: prototypes and personal tools can tolerate the unofficial route; anything customer-facing or revenue-bearing should be on the official API.
Which path should you choose?
The decision is mostly about stakes and scale. Use the WhatsApp Cloud API for anything production or commercial: it is compliant, scales cleanly, has a free development sandbox, and carries no ban risk. Reach for Baileys when you want a lightweight prototype or a personal project, you don't want a browser running, and you accept that the number could be banned. Pick whatsapp-web.js when you specifically need WhatsApp Web feature parity and don't mind the Chromium footprint — but understand it carries the same ban risk as Baileys.
Whichever you choose, the engineering fundamentals are the same: handle messages idempotently, keep secrets out of source control, respond to webhooks fast, and never send unsolicited bulk messages. The platform decision sets your ceiling on safety and scale; your behavior decides whether you stay within it.
Frequently asked questions
However you build your bot, only message numbers that are actually on WhatsApp. Our hosted API tells you if a number is registered and returns the public profile picture, display name, about text, and business flag from a single HTTP call — read-only, no QR scan, no ban risk.
Explore the WhatsApp Profile APINeed cheap numbers to create, verify or warm up WhatsApp accounts for testing? GrizzlySMS rents virtual numbers for WhatsApp verification from under $1.
Get a WhatsApp number