DEV Community

Cover image for Building an Open-Source WhatsApp Customer Support Stack (n8n + Chatwoot + WAHA)
אחיה כהן
אחיה כהן

Posted on

Building an Open-Source WhatsApp Customer Support Stack (n8n + Chatwoot + WAHA)

Most WhatsApp automation guides end with "sign up for this SaaS." This one doesn't. I'll walk you through setting up a fully self-hosted WhatsApp customer support system using three open-source tools — no per-message fees, no per-seat pricing, no vendor lock-in.

The Stack

Tool Role Replaces
n8n Workflow automation Zapier, Make.com
Chatwoot Customer support platform Intercom, Zendesk
WAHA WhatsApp API (self-hosted) Twilio, 360dialog

Total hosting cost: ~$50/month on a basic VPS (2GB RAM, 1 vCPU). Compare that to $200+/month for equivalent SaaS tools.

Why This Combination Works

Each tool does one thing well:

  • WAHA connects to WhatsApp and exposes a REST API for sending/receiving messages. No Meta Business verification required, no per-message charges.
  • Chatwoot gives your support team a proper inbox — conversation routing, canned responses, agent assignments, performance dashboards. Your team works in Chatwoot, not in WhatsApp directly.
  • n8n ties everything together — routing logic, AI responses, CRM updates, notifications. It's the "brain" that decides what happens when a message arrives.

The key insight: WAHA handles the transport, Chatwoot handles the human interface, and n8n handles the logic. Clean separation of concerns.

Architecture Overview

Customer (WhatsApp)
       ↓
     WAHA (receives message via webhook)
       ↓
     n8n (processes: route, enrich, auto-reply, or escalate)
       ↓
   Chatwoot (agent sees conversation, responds)
       ↓
     n8n (catches outgoing message)
       ↓
     WAHA (sends reply back to WhatsApp)
Enter fullscreen mode Exit fullscreen mode

Messages flow through n8n in both directions. This gives you full control over what happens at each step — you can add AI classification, auto-responses for FAQs, business hours logic, CRM lookups, or anything else.

Prerequisites

  • A VPS with Docker and Docker Compose (Ubuntu 22.04+ recommended)
  • A spare phone number for WhatsApp (or use your existing one)
  • Basic familiarity with Docker

Step 1: Set Up WAHA

WAHA runs as a Docker container and connects to WhatsApp Web:

# docker-compose.waha.yml
services:
  waha:
    image: devlikeapro/waha-plus:latest
    container_name: waha
    restart: unless-stopped
    ports:
      - "3000:3000"
    environment:
      - WHATSAPP_DEFAULT_ENGINE=WEBJS
      - WAHA_DASHBOARD_ENABLED=true
      - WAHA_DASHBOARD_USERNAME=admin
      - WAHA_DASHBOARD_PASSWORD=${WAHA_PASSWORD}
    volumes:
      - waha_data:/app/.sessions

volumes:
  waha_data:
Enter fullscreen mode Exit fullscreen mode

Start it:

docker compose -f docker-compose.waha.yml up -d
Enter fullscreen mode Exit fullscreen mode

Open http://your-server:3000/dashboard, scan the QR code with your phone, and WAHA is connected.

Configure the webhook

In the WAHA dashboard (or via API), set the webhook URL to point to your n8n instance:

curl -X POST http://localhost:3000/api/sessions/default/webhooks \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-n8n-domain.com/webhook/whatsapp-incoming",
    "events": ["message"]
  }'
Enter fullscreen mode Exit fullscreen mode

Step 2: Set Up Chatwoot

Chatwoot needs PostgreSQL and Redis:

# docker-compose.chatwoot.yml
services:
  chatwoot-web:
    image: chatwoot/chatwoot:latest
    container_name: chatwoot-web
    restart: unless-stopped
    depends_on:
      - chatwoot-postgres
      - chatwoot-redis
    ports:
      - "3001:3000"
    environment:
      - RAILS_ENV=production
      - SECRET_KEY_BASE=${CHATWOOT_SECRET}
      - FRONTEND_URL=https://your-chatwoot-domain.com
      - DATABASE_URL=postgresql://chatwoot:${DB_PASSWORD}@chatwoot-postgres:5432/chatwoot
      - REDIS_URL=redis://chatwoot-redis:6379
    command: bundle exec rails s -b 0.0.0.0 -p 3000

  chatwoot-worker:
    image: chatwoot/chatwoot:latest
    container_name: chatwoot-worker
    restart: unless-stopped
    depends_on:
      - chatwoot-postgres
      - chatwoot-redis
    environment:
      - RAILS_ENV=production
      - SECRET_KEY_BASE=${CHATWOOT_SECRET}
      - DATABASE_URL=postgresql://chatwoot:${DB_PASSWORD}@chatwoot-postgres:5432/chatwoot
      - REDIS_URL=redis://chatwoot-redis:6379
    command: bundle exec sidekiq -C config/sidekiq.yml

  chatwoot-postgres:
    image: postgres:15
    container_name: chatwoot-postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=chatwoot
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=chatwoot
    volumes:
      - chatwoot_pg:/var/lib/postgresql/data

  chatwoot-redis:
    image: redis:7-alpine
    container_name: chatwoot-redis
    restart: unless-stopped
    volumes:
      - chatwoot_redis:/data

volumes:
  chatwoot_pg:
  chatwoot_redis:
Enter fullscreen mode Exit fullscreen mode

After starting, run the database setup:

docker exec chatwoot-web bundle exec rails db:chatwoot_prepare
Enter fullscreen mode Exit fullscreen mode

Create an API channel in Chatwoot

  1. Log in to Chatwoot → Settings → Inboxes → Add Inbox
  2. Choose API as the channel type
  3. Name it "WhatsApp"
  4. Save the Inbox ID and generate an API access token — you'll need both for n8n

Step 3: Set Up n8n

n8n connects everything:

# docker-compose.n8n.yml
services:
  n8n:
    image: n8nio/n8n:latest
    container_name: n8n
    restart: unless-stopped
    ports:
      - "5678:5678"
    environment:
      - N8N_HOST=your-n8n-domain.com
      - N8N_PROTOCOL=https
      - WEBHOOK_URL=https://your-n8n-domain.com/
      - N8N_ENCRYPTION_KEY=${N8N_ENCRYPTION_KEY}
      - DB_TYPE=postgresdb
      - DB_POSTGRESDB_HOST=n8n-postgres
      - DB_POSTGRESDB_DATABASE=n8n
      - DB_POSTGRESDB_USER=n8n
      - DB_POSTGRESDB_PASSWORD=${N8N_DB_PASSWORD}
    volumes:
      - n8n_data:/home/node/.n8n
    depends_on:
      - n8n-postgres

  n8n-postgres:
    image: postgres:15
    container_name: n8n-postgres
    restart: unless-stopped
    environment:
      - POSTGRES_USER=n8n
      - POSTGRES_PASSWORD=${N8N_DB_PASSWORD}
      - POSTGRES_DB=n8n
    volumes:
      - n8n_pg:/var/lib/postgresql/data

volumes:
  n8n_data:
  n8n_pg:
Enter fullscreen mode Exit fullscreen mode

Step 4: Build the Workflow in n8n

Here's where it all comes together. You need two workflows:

Workflow 1: Incoming Messages (WhatsApp → Chatwoot)

  1. Webhook node — listens at /webhook/whatsapp-incoming
  2. Function node — extracts sender phone, message text, media URLs
  3. HTTP Request node — creates/finds contact in Chatwoot via API
  4. HTTP Request node — creates conversation (or finds existing one)
  5. HTTP Request node — posts the message to Chatwoot

The core logic in the Function node:

const body = $input.first().json.body;
const payload = body.payload;

return {
  phone: payload.from.replace('@c.us', ''),
  message: payload.body || '',
  hasMedia: payload.hasMedia || false,
  mediaUrl: payload.media?.url || null,
  timestamp: payload.timestamp
};
Enter fullscreen mode Exit fullscreen mode

Workflow 2: Outgoing Messages (Chatwoot → WhatsApp)

  1. Webhook node — Chatwoot sends a webhook when an agent replies
  2. IF node — filter for message_created events where message_type === 'outgoing'
  3. Function node — extract the reply text and recipient phone
  4. HTTP Request node — send via WAHA API:
// WAHA send message endpoint
const response = await $http.post('http://waha:3000/api/sendText', {
  chatId: `${phone}@c.us`,
  text: message,
  session: 'default'
});
Enter fullscreen mode Exit fullscreen mode

Bonus: Add AI Auto-Responses

Insert an AI Agent node between steps 2 and 3 in Workflow 1:

  1. Classify the message (FAQ, complaint, sales inquiry, urgent)
  2. For FAQ → auto-respond with a canned answer
  3. For everything else → route to Chatwoot for human handling

This handles the easy 60-70% of messages automatically while your team focuses on conversations that actually need a human.

What You Get

After setup, your support team sees this workflow:

  1. Customer sends a WhatsApp message
  2. n8n receives it, optionally auto-responds to FAQs
  3. If it needs a human — appears in Chatwoot inbox
  4. Agent responds in Chatwoot
  5. Reply goes back through n8n → WAHA → WhatsApp

Your team never touches WhatsApp directly. They work in a proper support tool with:

  • Conversation assignment and routing
  • Canned responses and macros
  • Customer conversation history
  • Performance metrics and SLA tracking
  • Multi-agent support (no more "who's handling this?")

Production Considerations

Reverse proxy: Put all three services behind Caddy or Nginx with SSL. Each gets its own subdomain.

Backups: PostgreSQL databases need daily backups. A simple cron job with pg_dump works fine.

Monitoring: n8n has built-in execution logging. Set up a workflow that alerts you (via email or Telegram) if any workflow fails.

Scaling: This stack handles hundreds of conversations per day on a single $50/month VPS. If you need more, n8n supports Queue Mode with separate worker nodes.

Rate limits: WhatsApp has rate limits on messages. WAHA respects these automatically, but if you're sending bulk messages (marketing campaigns), add delays in your n8n workflows.

Cost Comparison

Setup Monthly Cost Per-message fees Per-seat fees
This stack (self-hosted) ~$50 None None
Twilio + Intercom + Zapier $300+ Yes ($0.005+/msg) Yes ($39+/seat)
MessageBird + Zendesk + Make $250+ Yes Yes ($55+/seat)

The self-hosted stack pays for itself after the first month if you have more than one support agent.

Wrapping Up

The n8n + Chatwoot + WAHA stack isn't the simplest setup — there's real DevOps involved. But once it's running, you get enterprise-grade WhatsApp support infrastructure at a fraction of the cost, with full control over your data.

I've been running this stack in production for multiple clients (clinics, e-commerce, service businesses) and it handles everything from appointment booking to order tracking to AI-powered FAQ responses.

If you want to see a live example of what this looks like in practice, I published a workflow template on n8n's community library that shows the pattern of connecting messaging APIs with AI processing.


Have questions about the setup? Drop a comment — happy to help with specific configuration issues.

Top comments (0)