Wavelink connects a company's WhatsApp, learns its catalogue and knowledge base, and answers customers in text or in voice notes — in English, Arabic, or Roman Urdu. Every new number becomes a lead in the built-in CRM, and your team can jump into any chat from a WhatsApp-style inbox inside the portal.
No AI vendor lock-in. Text runs on DeepSeek or your own open-weights model. Speech-to-text, text-to-speech, voice cloning, and image OCR all run on your own server.
On a fresh Ubuntu/Debian VPS (8 GB RAM, Docker installed automatically if missing):
curl -fsSL https://raw.githubusercontent.com/DeveloperSarim/wavelink/main/install.sh | bashThe installer asks for an admin email and password, then does everything else — generates hardened secrets, clones into a fresh folder, builds the stack, boots it, and prints your login URL. Five minutes and you're in.
Prefer to do it by hand? See Manual install. Want a domain + HTTPS? See Going to production with a domain.
|
🤖 An agent that actually knows the business Retrieval-augmented answers over your catalogue, uploaded documents, and per-contact memory — grounded, not hallucinated. Gated tools let it quote prices, share items, and book only what you allow. 🎙️ Voice-native, both ways Understands inbound voice notes (faster-whisper STT) and replies with a natural voice note when it fits (Piper / XTTS-v2). Male/female voices, per-company defaults, and voice cloning. 🖼️ Reads images Customers send receipts, screenshots, and IDs — the agent OCRs the text (Urdu/Arabic/English) and answers about it. 🌍 Trilingual English, Arabic, and Roman Urdu — detected per message, with native-accent voice output. |
📇 CRM that fills itself Every new WhatsApp number becomes a lead. Drag it across a kanban pipeline, log deals and revenue per client, and watch conversion in real time. 💬 Shared team inbox A WhatsApp-style inbox with live updates, voice playback, and one-click human takeover. 🏢 True multi-tenancy A super-admin creates companies with time-boxed plans, usage limits, and provider toggles. Tenant isolation is absolute — no company can read another's data. 🔒 Yours to run Everything is Docker. Postgres + pgvector, Redis, MinIO, and your models — all on your box. Bring your own DeepSeek key or run a local LLM. Flip between them with no redeploy. |
"Customers message at 2am and wake up to answers. It handles voice notes in Urdu better than some of my staff — and every chat is already a lead." — Ali R., real-estate agency, Lahore
"We resell digital subscriptions on WhatsApp. Wavelink reads the payment screenshots, replies instantly, and the revenue tracking on each client is exactly what I was doing in a spreadsheet." — Hamza, e-commerce store owner
"Set it up on my own VPS in an afternoon. No per-message fees, no data leaving my server, and I can white-glove each client from one inbox." — Sana K., digital agency
| Role | Who | Scope |
|---|---|---|
SUPER_ADMIN |
you, the platform owner | create companies, set access duration, suspend/enable, reset passwords, toggle AI providers, set & watch per-company usage and cost |
COMPANY_ADMIN |
your customer | everything inside their own company |
COMPANY_AGENT |
their staff | inbox and CRM only |
┌─────────────────────────┐
WhatsApp customer ────────► │ WhatsApp (Baileys) │
(text / voice / image) │ services/whatsapp:4001 │
│ multi-tenant sessions │
└───────────┬─────────────┘
│ POST /api/v1/internal/wa/inbound
│ (header: x-internal-key)
▼
┌───────────────┐ HTTP ┌────────────────────────────────┐ BullMQ ┌──────────────┐
│ Next.js web │ ────────► │ Fastify API apps/api:4000 │ ─────────► │ worker │
│ apps/web │ ◄──────── │ REST /api/v1 + Socket.IO │ │ ai:reply │
│ │ websocket└──────┬───────────────┬─────────┘ │ embeddings │
└───────────────┘ │ │ │ outbound │
│ │ └──────┬───────┘
┌──────────▼──────┐ ┌─────▼───────┐ │
│ PostgreSQL 17 │ │ Redis 7 │ │
│ + pgvector │ │ queue/cache │ │
└─────────────────┘ └─────────────┘ │
│
┌────────────────────────┬────────────────────────────────────┤
▼ ▼ ▼
┌─────────────────────┐ ┌────────────────────────────┐ ┌───────────────────────┐
│ Text provider │ │ services/voice:8000 │ │ MinIO (S3) :9000 │
│ DEEPSEEK (hosted) │ │ Piper · fast CPU TTS │ │ voice notes, images, │
│ LOCAL services/llm│ │ XTTS-v2 · voice cloning │ │ catalogue PDFs │
│ OpenAI-compatible │ │ faster-whisper · STT │ └───────────────────────┘
└─────────────────────┘ │ tesseract · image OCR │
└────────────────────────────┘
Inbound turn: receive → (voice? → STT) · (image? → OCR) → detect language
→ build context (RAG over pgvector + catalogue + memory + last 20 msgs)
→ LLM + gated tools → choose text or voice → TTS → MinIO → send
→ persist, emit socket event, update lead, record usage.
In production everything sits behind a single nginx (infra/nginx/nginx.conf) — the only
thing with a published port: / → web, /api → api, /socket.io → api (websocket), /media
→ MinIO.
| Layer | Stack |
|---|---|
| Web | Next.js 15 · React 19 · Tailwind · TanStack Query |
| API / workers | Fastify 5 · Prisma 6 · BullMQ 5 · Socket.IO · Zod |
| Data | PostgreSQL 17 + pgvector · Redis 7 · MinIO (S3) |
| Baileys (multi-device) · optional Evolution API adapter | |
| Voice / vision | Python · FastAPI · Piper · XTTS-v2 · faster-whisper · Tesseract |
| Models | DeepSeek API or local OpenAI-compatible server (llama.cpp / vLLM) |
| Infra | Docker Compose · nginx |
| Minimum | Notes | |
|---|---|---|
| Docker Engine | 24+ | with the Compose v2 plugin |
| Docker Compose | 2.24+ | the prod override uses the !override tag |
| RAM | 8 GB free | 16 GB if you enable the local LLM profile |
| Disk | 20 GB free | model weights and media grow fast |
The default stack (Piper TTS + small Whisper + DeepSeek) runs comfortably on an 8-core / 16 GB
KVM VPS with no GPU.
Rather run it yourself? Four commands:
git clone https://github.com/DeveloperSarim/wavelink.git /opt/wavelink && cd /opt/wavelink
cp .env.example .env # then edit — see below
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d --build
docker compose exec api npm run db:deploy && docker compose exec api npm run db:seedBefore you boot, change every secret in .env:
NODE_ENV=production
COOKIE_SECURE=false # true once you're behind HTTPS
APP_URL=http://YOUR_SERVER_IP
CORS_ORIGINS=http://YOUR_SERVER_IP
NEXT_PUBLIC_API_URL=http://YOUR_SERVER_IP/api/v1
NEXT_PUBLIC_SOCKET_URL=http://YOUR_SERVER_IP
S3_PUBLIC_URL=http://YOUR_SERVER_IP/media
JWT_ACCESS_SECRET=$(openssl rand -hex 48)
JWT_REFRESH_SECRET=$(openssl rand -hex 48)
APP_ENCRYPTION_KEY=$(openssl rand -hex 32) # exactly 64 hex chars
INTERNAL_API_KEY=$(openssl rand -hex 32)
POSTGRES_PASSWORD=... # also update DATABASE_URL
MINIO_ROOT_PASSWORD=... # also update S3_SECRET_KEY
SEED_ADMIN_EMAIL=you@example.com
SEED_ADMIN_PASSWORD=...
NEXT_PUBLIC_*values are compiled into the web bundle at build time — change them and you mustdocker compose build webagain..env.exampleis the authoritative list of every variable.
Then open http://YOUR_SERVER_IP and sign in with your SEED_ADMIN_* credentials.
Wavelink serves everything through one HTTP port. To put it on app.example.com with free HTTPS,
point a reverse proxy at that port. The easiest is Caddy (automatic Let's Encrypt):
# /etc/caddy/Caddyfile
app.example.com {
reverse_proxy localhost:80 # or whatever port you chose
}Already running Apache or nginx? Proxy app.example.com → http://127.0.0.1:<port> and pass the
WebSocket upgrade through for /socket.io. However you terminate TLS, once HTTPS is live update
.env to the https://app.example.com origin across APP_URL, CORS_ORIGINS, S3_PUBLIC_URL,
NEXT_PUBLIC_API_URL, NEXT_PUBLIC_SOCKET_URL, set COOKIE_SECURE=true, then
docker compose ... up -d --build web api worker.
Sign in as a company admin → WhatsApp → Connect a number → scan the QR from WhatsApp →
Linked devices. Session credentials are encrypted at rest with APP_ENCRYPTION_KEY and
persist on the wa-auth volume, so restarts don't log the tenant out. The number self-heals and
answers 24/7 — no need to keep the portal open. Use a spare number for testing; WhatsApp doesn't
love automation on a personal line.
Both speak the OpenAI chat-completions API; the switch is runtime, no redeploy.
- DeepSeek (fastest to start): add
DEEPSEEK_API_KEY(or paste it in the console — stored encrypted), thenSUPER_ADMIN → Providers → enable DeepSeek → set default → Test. - Local model:
docker compose --profile llm up -d llmdownloads a GGUF model onto a volume, thenSUPER_ADMIN → Providers → enable Local LLM. Requests now stay entirely on your box.
Out of the box: Piper on CPU (real-time voice notes on 8 cores) + faster-whisper
(WHISPER_MODEL=small) for inbound speech. On an NVIDIA box, run the gpu profile for XTTS-v2 on
CUDA. Per-company voice (engine, gender, TEXT/VOICE/AUTO) lives under Agent → Voice.
Three things carry state: PostgreSQL, the MinIO bucket, and the WhatsApp session volume.
make backup # pg_dump + a mirror of the media bucket into ./backupsThe WhatsApp auth state lives on the wavelink_wa-auth volume — back it up too, or every tenant
re-scans their QR. A sensible cron:
15 3 * * * cd /opt/wavelink && make backup >> /var/log/wavelink-backup.log 2>&1Copy ./backups off the machine. A backup on the same disk is not a backup.
Login shows "Network error"
Your browser cached an old bundle (or you're on HTTP with COOKIE_SECURE=true). Hard-refresh
(Cmd/Ctrl+Shift+R). If you just moved to a domain, rebuild web so the baked NEXT_PUBLIC_* URLs
match the new origin.
api restarts in a loop
docker compose logs api. Almost always a config error — core/config.ts parses .env with Zod
and exits on the first bad variable. Usual culprit: APP_ENCRYPTION_KEY not exactly 64 hex chars.
Images / voice notes 404, or WhatsApp shows a broken attachment
S3_PUBLIC_URL must be reachable from a browser (and WhatsApp's servers) — not http://minio:9000.
It should match your public origin, e.g. https://app.example.com/media.
The inbox / board doesn't update live
Socket.IO is failing. NEXT_PUBLIC_SOCKET_URL must be your public origin, and the reverse proxy
must forward the WebSocket upgrade for /socket.io.
WhatsApp keeps dropping to QR_PENDING
The wa-auth volume was wiped or the number was unlinked. Reconnect from the portal. If it loops
within minutes, WhatsApp has rate-limited the number — wait a few hours.
Start over completely
make reset destroys every volume, rebuilds, migrates, and reseeds.
apps/api Fastify API, BullMQ workers, Socket.IO, Prisma schema + seed
apps/web Next.js 15 portal (admin console + company portal + inbox)
services/whatsapp Baileys multi-tenant session host
services/voice FastAPI: Piper + XTTS-v2 TTS, faster-whisper STT, Tesseract OCR, cloning
services/llm Local OpenAI-compatible model server + LoRA finetune scripts
packages/shared Types, enums, and Zod schemas shared by API and web
infra postgres init SQL, nginx config, MinIO bootstrap
install.sh One-command installer
.env.example is the single source of truth for configuration. SPEC.md is the canonical build
contract.
npm run dev # shared watch + api + worker + web (local dev)
npm run typecheck # typecheck every workspace
make help # every Docker/ops shortcut
make backup | reset # back up state · nuke and re-seedIssues and PRs welcome. Please run npm run typecheck before opening a PR, and keep changes
scoped. New configuration goes in .env.example with a comment. By contributing you agree to the
license terms below.
Source-available, not open source. See LICENSE. In short — you may view the code and self-host one instance for your own use, but you may not resell, sublicense, rebrand, white-label, or offer Wavelink as a service to others. The Wavelink name and branding must stay intact. For commercial or white-label licensing, contact the author (DeveloperSarim).
You are responsible for your own use, including WhatsApp's and any AI provider's terms.
Built with ☕ and a waveform by DeveloperSarim. Wavelink · WhatsApp AI agents.