Baileys is the open-source TypeScript library most people reach for when they want to talk to WhatsApp from Node.js without a browser. It is good at what it does, and a working bot takes an afternoon. What takes months is everything a number needs to stay connected and useful once real customers depend on it. This page lists those pieces, what building each one around Baileys involves, and what wuapi runs instead.
- Advantage: Proxies, reconnects and sends through drops are run for you.
- Advantage: Sessions are encrypted and can move to another worker without a new QR code.
- Advantage: A REST API with signed, retried webhooks around the number.
- Advantage: No upgrades on your side when the protocol changes.
- Disadvantage: It costs money per number, where the library is free.
- Disadvantage: You get what the API exposes, not every protocol feature the day it lands.
- Disadvantage: Hosted: no fit for software your users run on their own machines.
- Advantage: Free and MIT licensed.
- Advantage: Every protocol feature, straight from the library.
- Advantage: A plain Node.js process: no browser to run.
- Advantage: Pairing by QR code or by a code typed on the phone.
- Disadvantage: The file auth state is not meant for production, per its docs.
- Disadvantage: Proxies, pacing, reconnects and monitoring are yours to build.
- Disadvantage: Breaking changes arrive with major versions, with a version 8 in the works.
#What Baileys is
Baileys, maintained under WhiskeySockets and MIT licensed, speaks the same WebSocket protocol WhatsApp Web uses, from a plain Node.js process. It is published on npm as baileys and @whiskeysockets/baileys. When we checked, the latest tag was 7.0.0-rc14, with 6.7.24 kept as the legacy line, and the maintainers describe a version 8 in active development with a new auth system.
The shape of a Baileys program is small:
- A socket.
makeWASocketopens the connection, with an optionalagentfor a proxy and afetchAgentfor media. - Auth state.
useMultiFileAuthStatekeeps the keys in a folder. The docs are direct about it: it is not recommended for production, and you should write an auth state backed by a real database. - Events.
messages.upsertfor new messages,connection.updatefor the QR code and the connection state,creds.updateto save keys. - Pairing. A QR code from
connection.update, orrequestPairingCodefor a code typed on the phone.
Its README carries a clear warning: the project is not affiliated with WhatsApp, and the maintainers do not condone spam, bulk or automated messaging that breaks WhatsApp's terms.
#Who should run Baileys themselves
There are good reasons to stay on the library.
- You are building something the API does not cover. A library gives you every protocol feature the day it lands. An API gives you what its maintainers chose to expose.
- One number, one bot, low stakes. A personal assistant or an internal alert bot does not need a fleet.
- Your product is open source or self-hosted. Your users run it on their own machines, so a hosted dependency does not fit.
- You have the team. If you already run proxies, on-call and a database for sessions, most of the list below exists.
If none of those is you, read on. Everything in the next section is work that stands between a Baileys demo and a number a customer relies on.
#The socket is the easy part
A connection to WhatsApp lives inside a hostile network. Residential proxies drop connections every few minutes, sockets go half-open and silent, and round trips spike to seconds. A number also has a reputation that WhatsApp reads from how it connects and how it sends. The sections below take each consequence in turn, with what wuapi ships for it. Every figure is from our code.
#A network exit per number
WhatsApp sees where a session connects from. A number that shows up from a datacenter, or from a new country every day, looks unlike its owner. Why WhatsApp bans numbers goes through that signal and the others.
What you build: a residential proxy provider, an exit per number in the owner's country, logic to keep each number on the same exit, and a rule for what happens when the proxy is down.
What wuapi does: every account needs a proxyLocation, a country and city from 134 countries. Before a number first logs in through a new exit, the engine tests three candidates and keeps the fastest. The exit is sticky across reconnects and restarts. If the proxy cannot be reached, the account stays offline: there is no path to a datacenter IP or a direct connection, by design. 0.5 GB of traffic a month per paid number is included, then $0.99 per GB.
#Changing an exit without looking like a bot
Exits die, and replacing one is itself a signal if it happens too often.
What you build: a rotation policy with a budget.
What wuapi does: a number moves to a new exit only after 3 failed connects, at most 4 times a day and at least 6 hours apart, with a hard ceiling of 8 a day. If the proxy provider's gateway fails for several numbers at once, new connections move to a gateway in another region and move back once the first answers again, without touching live sockets and without spending any number's budget. Sessions dial the proxy over SOCKS5, which we measured to stay healthier than HTTP tunnels.
#Noticing a dead socket fast
The worst failure is the quiet one: a socket that looks open and delivers nothing.
What you build: your own liveness checks on top of the keepalive, and deadlines on every wait.
What wuapi does: an adaptive liveness ping with a 6 to 20 second deadline, a keepalive with a hard limit, and a send that gets no acknowledgement from WhatsApp within 20 seconds recycles the connection. The next connection is dialed ahead so the swap is quick.
#Reconnecting without looping
What you build: a reconnect loop that is patient with a flaky network and stops dead on a real logout. The Baileys docs show the basic version: reconnect unless the reason is loggedOut.
What wuapi does: the first retry is immediate, then an exponential backoff with full jitter capped at 20 seconds, and it never gives up on a temporary drop. It recycles the connection in place first and rebuilds the session only after two failures. It does not reconnect after logged_out, connection_replaced or temporary_ban, and on client_outdated it refreshes the version once. When many numbers drop at the same moment, the engine treats it as a network incident: it spreads the reconnects out and charges no exit budget.
#Sends that survive a drop
What you build: a queue per number that holds a message through a disconnect, retries it, and never delivers it twice.
What wuapi does: each number sends one message at a time. A send made during a drop waits for the number and is retried with the same WhatsApp message id, so it cannot arrive twice. Idempotency-Key on any POST is kept for 24 hours, so a retry from your side is safe too.
#Checking the recipient first
What you build: a lookup before writing to someone new, because messages to numbers that are not on WhatsApp are wasted and look careless.
What wuapi does: before a first message to a contact, the engine checks the number is on WhatsApp and fails the send with not_on_whatsapp if not. Answers are cached, 7 days for a yes and 6 hours for a no. This is always on.
#Pacing like a person
What you build: per-number rate limits, a stricter one for strangers, and a typing indicator.
What wuapi does: a per-minute cap from 0 to 30 with a random 1 to 3 second gap between sends, a separate cap for contacts who never wrote to the number, and a typing indicator sized to the message, between 0.8 and 6 seconds by default. These are off by default so replies are instant. We recommend 12 a minute, 5 to new contacts and typing on for anything that starts with you. Keep your number healthy explains when.
#Keeping sessions safe and movable
What you build: an auth state in a database, backups, encryption, and a rule that two processes never open the same session, because that looks like a takeover and logs the device out.
What wuapi does: session keys are encrypted with AES-256-GCM before they are stored, and a session can be restored on another worker without a new QR code. Writes are fenced so a stale worker cannot overwrite a newer session, and a deploy moves sessions one at a time.
#Webhooks, media and alerts
What you build: your own events to your own services, media handling, and a pager.
What wuapi does: webhooks signed with HMAC-SHA256 over the timestamp and raw body, retried at 30s, 2m, 10m, 1h and 6h, with 30 days of delivery logs. Media is sent by URL, fetched once from public addresses up to 100 MB, with uploads under deadlines and a cache for files you send repeatedly. account.disconnected fires only after a 20 second grace, and the dashboard emails you or posts to Slack when a number stays offline past a threshold you set.
#The work, side by side
| piece | Baileys on your server | wuapi |
|---|---|---|
| protocol | the library | handled |
| session storage | your auth state, database, backups | encrypted, restorable on another worker |
| residential exit | your provider, per number | included, sticky, in the number's country |
| proxy down | your rule | offline, never a datacenter IP |
| exit rotation | your policy | after 3 failures, 4 a day, 6 hours apart |
| dead socket detection | your checks | liveness ping, 20 second send deadline |
| reconnects | your loop | jittered backoff, stops on logout or ban |
| sends through a drop | your queue | wait, retry with the same message id |
| recipient check | your lookup | always on, cached |
| pacing and typing | your limiter | per number, opt-in |
| webhooks | your events | signed, retried six times |
| upgrades | yours, on protocol changes | ours |
| price | free library; servers, proxies, time | $6 first number, $4.50 each to 50 |
#Moving a Baileys bot to wuapi
Most of the code you delete. What remains is a webhook handler and API calls.
// Baileys: you hold the socket
import makeWASocket, { DisconnectReason, useMultiFileAuthState } from "baileys"
const { state, saveCreds } = await useMultiFileAuthState("auth")
const sock = makeWASocket({ auth: state })
sock.ev.on("creds.update", saveCreds)
sock.ev.on("connection.update", ({ connection, lastDisconnect }) => {
// reconnect unless logged out, and everything this page lists
})
sock.ev.on("messages.upsert", async ({ messages }) => {
for (const m of messages) {
if (!m.key.fromMe) await sock.sendMessage(m.key.remoteJid!, { text: "Got it, thanks." })
}
})
// wuapi: you handle an event
if (event.type === "message.received") {
await wuapi.messages.send({ accountId: event.data.object.accountId, to: event.data.object.from, text: "Got it, thanks." })
}| Baileys | wuapi |
|---|---|
| auth folder or database | account, stored for you |
connection.update with qr | GET /v1/accounts/{accountId}, read qrCodeUrl |
requestPairingCode | POST /v1/accounts/{accountId}/pairing-code |
sendMessage(jid, { text }) | POST /v1/messages with to, text |
messages.upsert | message.received |
connection.update | account.connected, account.disconnected |
5511999999999@s.whatsapp.net | +5511999999999 |
Link each number again with a QR code or a pairing code; a session cannot be carried from your auth folder to our store. Verify each webhook as the docs describe. The Free plan runs one number with 2,000 messages a month and no card, enough to run next to your bot for a week.
#Questions people ask
Is Baileys free?
Yes. Baileys is MIT licensed and free to use. What costs money is running it: servers, residential proxies for each number, a database for sessions, and the time someone spends on reconnects, upgrades and numbers that go quiet.
Is Baileys safe to use in production?
The library itself is widely used, but its docs say the file-based auth state is not meant for production, and its README warns against bulk or automated messaging. Production needs a database-backed auth state, proxies, pacing and monitoring you build yourself.
Does wuapi use Baileys?
We do not publish which protocol library our engine uses. What we publish is the behaviour around it: the proxy, reconnects, pacing, the recipient check and signed webhooks, each with the values from our code.
Can I keep using Baileys for some numbers and wuapi for others?
Yes. A number is linked to one place at a time, but nothing stops you running some numbers on your own Baileys service and others on wuapi, and moving them one by one.
Will wuapi stop my numbers from getting banned?
No one can promise that. WhatsApp restricts numbers for what they send and how people react. wuapi removes the network and connection signals a self-hosted setup often gets wrong, and gives you pacing to turn on.