Menu
AboutSkillsExperienceWorkContactDownload CV
← All work
In production2025 — nowPersonal project

WhatsApp Gateway

A Node/TypeScript service that owns a single WhatsApp session, stores every conversation in SQLite and hands scoped access to other projects through a REST API — plus a runner that turns a chat into a working assistant.

88k+
messages stored
7
services sharing one session
1
WhatsApp connection to maintain
  • Node.js
  • TypeScript
  • Baileys
  • Express 5
  • better-sqlite3
  • Docker
  • Python
  • systemd

The problem

Every project that wanted to send a WhatsApp message was about to grow its own client, its own session and its own way of leaking into chats it had no business reading. One of those is maintainable; five is not.

My role

Sole author — gateway, REST API, permission model, dashboard and the bot runner.

What I built

  1. 01

    A single Baileys session in Docker, with history sync, media download and identity resolution, writing conversations, messages and contacts to SQLite.

  2. 02

    A REST API built for consumers that come and go: cursor-based tailing on a monotonic sequence so a service can disappear for a day and resume exactly where it stopped.

  3. 03

    An agent model instead of one shared secret — every service gets its own hashed token plus per-chat grants for read, send and media, so a store bot cannot read family chats.

  4. 04

    A dashboard for the human side: analytics, chat browsing, and a switch per agent that revokes access instantly.

  5. 05

    A shared Python runner that attaches an assistant to a chat — it answers bursts as one turn, keeps its own memory in git, schedules reminders and jobs, transcribes voice notes, and strips secrets from anything it says.

Outcome

  • Five assistants and two notification-only services run on it today: a store bot that answers customers, two family group assistants with scoped IoT control, and my own assistant.

  • A new bot is now an agent, a token and a markdown file — no new integration code.

  • Nothing but the dashboard is exposed publicly; the public route refuses bearer tokens outright so a leaked token is useless from outside.