← ALL POSTS
10 MARCH 2026

Adding WhatsApp To The Gateway With Baileys, And Why We Put Auth In Mongo

SMS gets read. WhatsApp gets read and replied to. Plenty of clients wanted both on the same number and in the same conversation thread, so the gateway grew a second channel.

We used Baileys, which talks WhatsApp Web's protocol directly. It works well. There is one design decision in it that you have to change before you can run it multi-tenant.

The file-based auth problem

Baileys ships with useMultiFileAuthState, which persists the session to a folder on disk. Perfect for a single bot on a single box.

We are not that. We are a multi-tenant gateway with a session per client, running in a container. File-based auth means:

  • sessions live on the container's filesystem, so a redeploy logs everybody out
  • two sessions mean two folders and a naming convention you will get wrong
  • nothing is queryable, so the dashboard cannot tell you who is actually connected

So we replaced it with a MongoDB-backed auth state. Same interface Baileys expects, credentials and keys stored per session in the database. A container restart now reconnects instead of demanding everyone rescan a QR code, and the dashboard can show real session state because it is just a collection.

That is the one change I would tell anyone to make on day one rather than day thirty.

The session lifecycle

A NestJS module with the full set: create, start, stop, logout, delete. Start brings a session up and emits a QR code. The client scans it with their phone. After that it auto-reconnects on its own.

The distinction that matters is stop versus logout. Stop closes the socket and keeps the credentials, so it comes back without a rescan. Logout throws the credentials away and the client has to pair again. Mixing those two up is how you end up asking a client to rescan a QR code on a Sunday.

Endpoints sit under /api/v1/whatsapp/sessions/*, and the dashboard got a page with session cards, the QR scanner flow and a send-message dialog, so somebody non-technical can pair a client without an engineer.

The gotcha that ate an hour

Baileys wants a pino logger. We passed it a plain object with the right-looking methods, because that usually works.

It does not work. You get TypeError: logger.child is not a function, because Baileys calls .child() on it to create scoped loggers. Pass it an actual pino instance.

Where this sits now

Same inbound handler framework as SMS, so a keyword that works over text works over WhatsApp with no extra code. That paid off immediately: when we built the "text a keyword, get a login code" flow, it worked on both channels the day it shipped.

One honest warning

WhatsApp does not love automation. A number of ours got restricted for what Meta described as spam or bulk messaging, and the fix is not clever engineering, it is moving that client to the official WhatsApp Business API and accepting the per-message cost.

If you build on Baileys, build it knowing that. It is excellent for conversational, user-initiated traffic where the customer texted first. It is the wrong tool for outbound campaigns, and treating it as a free bulk channel is how you lose the number.

Want this working on your store? Aiva does the sales, marketing and service work - in your voice, around the clock.
Get Aiva