WhatsApp Bot with Local AI
What this article covers
- Two approaches to building a WhatsApp bot: official Business API vs. unofficial libraries.
- Practical example using whatsapp-web.js/Baileys with Ollama.
- QR pairing, session persistence, group operation.
- Legal and technical risks, and when to use which approach.
- Extensions: RAG, images, voice messages, moderation.
Introduction
WhatsApp is Germany’s most popular messenger, but it’s also the least bot-friendly platform. There is no official free bot API for private accounts. Two paths exist:
- WhatsApp Business Platform (Cloud API): Official, stable, but complex. Requires a Meta Developer account, business verification, template messages that need approval, and costs per conversation.
- Unofficial libraries (whatsapp-web.js, Baileys): Simulate WhatsApp Web, work with regular accounts, but violate the terms of service and carry account suspension risk.
This article walks through both approaches and explains when each makes sense.
Common use cases
- Internal team assistant: Employees query the bot in WhatsApp, internal documentation via RAG.
- First-level customer support: Answer frequent questions, escalate complex issues to humans.
- Notifications: Server alerts, appointments, order status delivered straight to your phone.
- Family or club bot: Retrieve information, maintain lists, send reminders.
- Public channels require caution: For public customer-facing channels, the Business API is the only safe option.
Approach 1: Unofficial Libraries (whatsapp-web.js / Baileys)
How it works
whatsapp-web.js (Node.js) and Baileys (TypeScript) speak the WhatsApp Web protocol. Your bot pairs like a second device via QR code. You need your own phone number (landlines work too: verification via call).
npm install whatsapp-web.js qrcode-terminal axios
Minimal bot with Ollama
const { Client, LocalAuth } = require('whatsapp-web.js');
const qrcode = require('qrcode-terminal');
const axios = require('axios');
const client = new Client({
authStrategy: new LocalAuth({ dataPath: './session' }),
puppeteer: { headless: true, args: ['--no-sandbox'] }
});
client.on('qr', qr => qrcode.generate(qr, { small: true }));
client.on('ready', () => console.log('Bot bereit'));
client.on('message', async msg => {
if (msg.fromMe || !msg.body) return;
// Respond only to direct messages or mentions
const chat = await msg.getChat();
if (chat.isGroup && !msg.body.startsWith('!ki')) return;
const prompt = msg.body.replace(/^!ki\s*/, '');
try {
const res = await axios.post('http://localhost:11434/api/chat', {
model: 'llama3.1:8b',
messages: [
{ role: 'system', content: 'Du bist ein hilfsbereiter Assistent. Kurze Antworten.' },
{ role: 'user', content: prompt }
],
stream: false
});
await msg.reply(res.data.message.content);
} catch (e) {
await msg.reply('Fehler: Ollama nicht erreichbar.');
}
});
client.initialize();
Start it, scan the QR code in the terminal with your phone (WhatsApp > Linked devices). Done.
Baileys variant (no headless browser)
Baileys implements the protocol natively, no Chromium required, lower RAM footprint:
const { makeWASocket, useMultiFileAuthState } = require('@whiskeysockets/baileys');
const { state, saveCreds } = await useMultiFileAuthState('./session');
const sock = makeWASocket({ auth: state });
sock.ev.on('creds.update', saveCreds);
sock.ev.on('messages.upsert', async ({ messages }) => {
const m = messages[0];
if (!m.message?.conversation || m.key.fromMe) return;
// Call Ollama as above, then:
await sock.sendMessage(m.key.remoteJid, { text: 'Antwort...' });
});
The risks, plain and simple
- Account suspension: Meta regularly bans unofficial clients. Use a separate number, never your main one.
- Pairing breaks: WhatsApp Web sessions expire. Your bot needs to handle re-pairing.
- No guaranteed uptime: Unsuitable for business customers. Your number can disappear at any time.
- E2EE remains intact: Messages stay end-to-end encrypted. The bot decrypts locally, which is good for privacy.
Approach 2: WhatsApp Business Platform (Cloud API)
Setup overview
- Meta Developer account > create app > add “WhatsApp”
- Verify a test number or your own business number (requires business verification)
- Set up a webhook endpoint on your server
- Generate a permanent access token
Receive messages (webhook)
from flask import Flask, request
import requests
app = Flask(__name__)
VERIFY_TOKEN = "dein-verify-token"
WA_TOKEN = "dein-permanent-token"
PHONE_ID = "deine-phone-number-id"
@app.route("/webhook", methods=["GET"])
def verify():
if request.args.get("hub.verify_token") == VERIFY_TOKEN:
return request.args.get("hub.challenge")
return "Forbidden", 403
@app.route("/webhook", methods=["POST"])
def receive():
data = request.json
for entry in data.get("entry", []):
for change in entry.get("changes", []):
for msg in change.get("value", {}).get("messages", []):
text = msg["text"]["body"]
sender = msg["from"]
answer = ask_ollama(text)
send_message(sender, answer)
return "OK", 200
def ask_ollama(prompt):
r = requests.post("http://localhost:11434/api/chat", json={
"model": "llama3.1:8b",
"messages": [{"role": "user", "content": prompt}],
"stream": False
})
return r.json()["message"]["content"]
def send_message(to, text):
requests.post(
f"https://graph.facebook.com/v19.0/{PHONE_ID}/messages",
headers={"Authorization": f"Bearer {WA_TOKEN}"},
json={
"messaging_product": "whatsapp",
"to": to,
"type": "text",
"text": {"body": text[:4000]}
}
)
Key limitations of the Business API
- 24-hour window: Free messages only if the user wrote in the last 24 hours. Otherwise only template messages (approved by Meta).
- Costs: Around 0.07-0.11 € per conversation in Germany (service conversations partly free since 2024, marketing higher).
- Verification required: Without a verified business account, you’re limited to 250 conversations per 24 hours.
Extensions
RAG: Document knowledge
Same pattern as the Telegram bot. Incoming message > embedding > Qdrant search > context to Ollama:
def ask_ollama_with_rag(prompt):
context = search_qdrant(prompt) # Top-3-Chunks
r = requests.post("http://localhost:11434/api/chat", json={
"model": "llama3.1:8b",
"messages": [
{"role": "system", "content": f"Nutze diesen Kontext:\n{context}"},
{"role": "user", "content": prompt}
],
"stream": False
})
return r.json()["message"]["content"]
Transcribe voice messages
WhatsApp voice messages (Opus OGG) > local Whisper > text > Ollama. With whatsapp-web.js: call msg.downloadMedia(). With Business API: use Media ID to download from the endpoint.
Group Operation
- In groups, respond only to a prefix (
!ki) or direct mentions, otherwise the bot reads every group chat message. - Admin list for moderation commands (
!kick,!warn). - Group ID whitelist: the bot should only be active in approved groups.
Human Handoff (Business API)
Use the “bot first, human fallback” pattern. When the AI is uncertain, escalate the conversation to a team member via internal notification or a ticket system like Chatwoot.
Deployment
whatsapp-web.js requires more resources due to Puppeteer and Chromium:
FROM node:20-slim
RUN apt-get update && apt-get install -y chromium fonts-liberation \
&& rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium
CMD ["node", "bot.js"]
services:
whatsapp-bot:
build: .
restart: always
volumes:
- wa_session:/app/session # Persist session!
environment:
- OLLAMA_URL=http://ollama:11434
volumes:
wa_session:
Critical: Use a session volume. Without persistence, you’ll need to scan the QR code again on every restart.
Legal Considerations
- GDPR: WhatsApp bots process phone numbers and chat content, which are personal data. For business use, document a privacy policy, a data processing agreement with Meta, and user consent.
- Terms of Service: Unofficial libraries violate WhatsApp’s Terms of Service, risking account suspension. Tolerated for hobby projects, not for business use.
- No Spam: Unsolicited messages violate competition law in Germany. Only respond to user requests.
Common Pitfalls
- QR code expires: Session volume not persisted, or the phone went offline. Use
LocalAuthwith a volume. - Account suspended: Too many messages sent too quickly, or users report the bot. Implement rate limiting and 2-5 second response delays.
- Chromium consumes RAM: whatsapp-web.js needs roughly 500MB or more. Alternative: Baileys (pure WebSocket protocol, no browser).
- 24-hour window forgotten: The Business API refuses free messages after 24 hours. Use templates or require the user to message first.
- Group IDs change: JIDs change when re-registering. Never hardcode group IDs.
Further Reading
- IRC-Coding.de: In-depth programming tutorials on Node.js, WebSocket protocols, and bot architectures.
- Telegram Bot: The simpler alternative with an official API.
- Signal Bot: Privacy-first alternative.
- Platform Comparison: Which platform for which use case?
- Ollama API: HTTP interface.
- Local RAG: Document knowledge for your bot.
Key Takeaways:
- WhatsApp has no free bot API: unofficial libraries (suspension risk) or Business API (complex, paid).
- Use whatsapp-web.js for hobby projects with a spare number; the Business API for customer contact.
- Persist your session, otherwise you’ll scan the QR code on every restart.
- Respect the 24-hour window with the Business API; templates require Meta approval.
- For most self-hosters, Telegram is the more pragmatic choice.
FAQ
Is a WhatsApp bot legal?
whatsapp-web.js or Baileys?
How do I avoid suspension?
What does the Business API cost?
Is encryption preserved?
Are there simpler alternatives?
Does the bot work without a phone?
Sources and Further Reading
- whatsapp-web.js: Library documentation.
- Baileys: WhatsApp Web API without a browser.
- WhatsApp Business Platform: Official Cloud API.
- IRC-Coding.de: Programming tutorials.


