← blog
July 10, 2026·2 min·41 views

An internal WhatsApp API with Baileys (no Chromium)

retour projetwhatsappdevopsnode.js
An internal WhatsApp API with Baileys (no Chromium)

Sending an OTP or a notification over WhatsApp is easy… until you look at the price of the official APIs and how painful their approval process is. For my projects (Mon Bara, TransitSoft), I needed an internal WhatsApp channel — lightweight and reusable. That's WhatsApp Internal API: a single building block that all my products consume through an SDK.

Baileys, no Chromium

A lot of solutions run WhatsApp Web inside a headless Chromium — heavy, fragile, and RAM-hungry. I went with Baileys, which speaks the WhatsApp protocol directly over WebSocket, no browser needed. The result: it runs on a tiny VPS without you ever thinking about it.

  • Server: Express + Socket.IO + Baileys + BullMQ (port 4000)
  • Dashboard: Next.js (create a session, scan the QR code, generate keys)
  • packages/shared: types + Zod schemas + socket events — source of truth
  • packages/sdk: the npm client @wa/sdk

Multi-session, with per-session keys

Each session = one WhatsApp number. You create it from the dashboard, scan the QR code with your phone, then generate an API key shown only once. A consuming project never sees Baileys: it just has a baseUrl and a key.

Rate limiting is survival

WhatsApp is quick to ban a number that spams. So every send goes through a rate-limited BullMQ queue (15 msg/min, configurable), with automatic retries (3 attempts, backoff) and controlled concurrency. Statuses come back in real time over Socket.IO:

queued → sending → sent → delivered → read | failed

A failure? You replay it from the dashboard, or it re-enqueues itself.

One line on the consumer side

This is where the SDK pays off. In any Node ≥ 18 project:

import { WhatsAppClient } from "@wa/sdk";
const wa = new WhatsAppClient({ baseUrl, apiKey: process.env.WA_API_KEY! });
await wa.sendOtp("237655388662", "482913");

The SDK is published by CI (GitLab) on an sdk-vX.Y.Z tag, and the service deploys behind Caddy (automatic TLS) — the compose file exposes nothing but localhost; postgres and redis stay internal.

What I took away from it

The best decision was making it a single internal building block instead of bolting WhatsApp onto every project. A queue + a rate limit + a typed SDK, and all my products send OTPs without reinventing the wheel — or risking a ban.

How do you handle transactional messaging (SMS, WhatsApp, email) on your end — a homemade service or a third-party provider? And how do you stay within the rate limits? Let me know.