Skip to content

Repository files navigation

Wavelink

Wavelink

Put an AI agent on your WhatsApp number — self-hosted, multi-tenant, voice-native.

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.



Self-hosted Docker TypeScript Next.js 15 License


⚡ Install in one command

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 | bash

The 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.


✨ What it does

🤖 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.


💬 What operators say

"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


🧭 Roles

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

🏗️ Architecture

                                   ┌─────────────────────────┐
      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.

Tech stack

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)
WhatsApp 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

📋 Prerequisites

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.


🛠️ Manual install

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:seed

Before 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 must docker compose build web again. .env.example is the authoritative list of every variable.

Then open http://YOUR_SERVER_IP and sign in with your SEED_ADMIN_* credentials.


🌐 Going to production with a domain

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.comhttp://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.


📱 Connecting a WhatsApp number

Sign in as a company admin → WhatsAppConnect 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.


🧠 Choosing the text model — DeepSeek or your own

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), then SUPER_ADMIN → Providers → enable DeepSeek → set default → Test.
  • Local model: docker compose --profile llm up -d llm downloads a GGUF model onto a volume, then SUPER_ADMIN → Providers → enable Local LLM. Requests now stay entirely on your box.

🎙️ Voice — CPU by default, GPU when you have one

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.


💾 Backups

Three things carry state: PostgreSQL, the MinIO bucket, and the WhatsApp session volume.

make backup    # pg_dump + a mirror of the media bucket into ./backups

The 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>&1

Copy ./backups off the machine. A backup on the same disk is not a backup.


🩺 Troubleshooting

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.


📂 Repository layout

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.

Handy commands

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-seed

🤝 Contributing

Issues 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.

📄 License

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.

About

Self-hosted, voice-native AI agent for WhatsApp — multi-tenant, trilingual (English/Arabic/Roman Urdu), with image OCR and a built-in CRM. Source-available.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages