The Complete Developer’s Guide to the Baileys WhatsApp Bot: Setup, Scaling, and VPS Deployment The LoopLearnX team built a custom WhatsApp AI bot using the `@whiskeysockets/baileys` library and Next.js, hosted on an Oracle Cloud VPS, to enable frictionless homework submissions for CBSE students. The system uses a two-tier architecture where a Node.js gateway maintains persistent WebSocket connections with WhatsApp servers while a Next.js serverless route handles authentication, student profile queries, intent classification via Gemini API, and Supabase database writes. The team found that integrating a self-hosted WhatsApp interface directly into their application was the single most critical driver of student engagement, as users could submit homework by simply photographing their physical notebooks. WhatsApp has become the default operating system for daily communication in regions like India. For modern web platforms—particularly in EdTech, local logistics, or localized services—forcing users to log into a complex desktop portal often results in a steep drop-off in user engagement. When building LoopLearnX an automated homework evaluation and tutoring tool for CBSE students , we realized that students rarely log in to a web dashboard on a desktop to upload their homework. Instead, they do their homework on physical notebooks, snap a picture, and expect instant grading. Integrating a custom, self-hosted WhatsApp interface directly into our Next.js application was not just a convenience—it was the single most critical driver of student engagement. This guide details the technical blueprint of how we built a resilient, memory-aware WhatsApp AI Bot using @whiskeysockets/baileys and Next.js, hosted on an Oracle Cloud VPS. We will cover the exact production failures we encountered, learnings learned, and why custom self-hosting beats off-the-shelf agent frameworks. For many demographics, WhatsApp represents friction-free engagement. Users don't need to remember passwords, manage active sessions, or learn a new user interface. By bringing our platform inside a messaging channel, we instantly enabled frictionless student homework submissions. To connect an application to WhatsApp, you have two primary routes: @whiskeysockets/baileys :To keep operations lightweight, we split the application into a two-tier architecture : Baileys to maintain WebSocket connections with WhatsApp servers 24/7. It listens to incoming messages, handles media download streams, and converts payloads into clean base64 data to pass forward. Student WhatsApp │ ▼ WebSocket 24/7 connection Node.js VPS Gateway Baileys + PM2 │ ▼ HTTP POST with x-bot-secret Next.js Serverless Route Vercel ├── 1. Authenticate Request ├── 2. Query Student Profile & History Supabase ├── 3. Classify & Evaluate Intent Gemini API └── 4. Write new Submission Record Supabase │ ▼ JSON Reply Node.js VPS Gateway Safe Queued Output ──► Sent back to Student WhatsApp index.js The core responsibilities of index.js on the VPS are maintaining the WebSocket session, managing authentication states, rendering QR codes for linking, and mounting an Express endpoint to monitor status. js // index.js require "dotenv" .config ; const { default: makeWASocket, useMultiFileAuthState, DisconnectReason, } = require "@whiskeysockets/baileys" ; const { Boom } = require "@hapi/boom" ; const pino = require "pino" ; const express = require "express" ; const qrcodeTerminal = require "qrcode-terminal" ; const qrcode = require "qrcode" ; const { handleIncomingMessage } = require "./bridge" ; const app = express ; const PORT = process.env.PORT || 3000; let sock = null; let botStatus = "starting"; let currentQrImage = null; async function connectToWhatsApp { // 1. Initialize multi-file authentication state const { state, saveCreds } = await useMultiFileAuthState "auth info baileys" ; sock = makeWASocket { auth: state, printQRInTerminal: false, // We render custom QR inside terminal & web UI logger: pino { level: "silent" } , } ; // 2. Listen for connection state updates sock.ev.on "connection.update", async update = { const { connection, lastDisconnect, qr } = update; if qr { botStatus = "qr needed"; // Render QR in terminal qrcodeTerminal.generate qr, { small: true } ; // Generate Data URL QR for web UI status page currentQrImage = await qrcode.toDataURL qr ; } if connection === "close" { const shouldReconnect = lastDisconnect?.error instanceof Boom ? lastDisconnect.error.output?.statusCode == DisconnectReason.loggedOut : true; botStatus = shouldReconnect ? "disconnected" : "logged out"; console.log "Connection closed. Reconnecting...", shouldReconnect ; if shouldReconnect { connectToWhatsApp ; } } else if connection === "open" { botStatus = "connected"; console.log "✅ WhatsApp WebSocket Connected successfully " ; } } ; // 3. Save updated credentials on session changes sock.ev.on "creds.update", saveCreds ; // 4. Mount incoming message listener sock.ev.on "messages.upsert", async m = { if m.type === "notify" { for const msg of m.messages { if msg.key.fromMe { await handleIncomingMessage sock, msg ; } } } } ; } // Simple web UI endpoint for linking & status monitoring app.get "/", req, res = { res.send