User sends WhatsApp message │ ▼ ┌─────────┐ │ WAHA │ (WhatsApp Web wrapper, self-hosted) └────┬────┘ │ Webhook POST ▼ ┌─────────┐ │ n8n │ (Workflow engine, self-hosted) └────┬────┘ │ Processes message, extracts lead data ▼ ┌──────────────┐ │ CRM/Database │ (Supabase, PostgreSQL, Google Sheets, etc.) └──────────────┘
User sends WhatsApp message │ ▼ ┌─────────┐ │ WAHA │ (WhatsApp Web wrapper, self-hosted) └────┬────┘ │ Webhook POST ▼ ┌─────────┐ │ n8n │ (Workflow engine, self-hosted) └────┬────┘ │ Processes message, extracts lead data ▼ ┌──────────────┐ │ CRM/Database │ (Supabase, PostgreSQL, Google Sheets, etc.) └──────────────┘
User sends WhatsApp message │ ▼ ┌─────────┐ │ WAHA │ (WhatsApp Web wrapper, self-hosted) └────┬────┘ │ Webhook POST ▼ ┌─────────┐ │ n8n │ (Workflow engine, self-hosted) └────┬────┘ │ Processes message, extracts lead data ▼ ┌──────────────┐ │ CRM/Database │ (Supabase, PostgreSQL, Google Sheets, etc.) └──────────────┘
docker run -d \ --name waha \ -p 3000:3000 \ -e WHATSAPP_DEFAULT_ENGINE=WEBJS \ -e WAHA_DASHBOARD_ENABLED=true \ devlikeapro/waha
docker run -d \ --name waha \ -p 3000:3000 \ -e WHATSAPP_DEFAULT_ENGINE=WEBJS \ -e WAHA_DASHBOARD_ENABLED=true \ devlikeapro/waha
docker run -d \ --name waha \ -p 3000:3000 \ -e WHATSAPP_DEFAULT_ENGINE=WEBJS \ -e WAHA_DASHBOARD_ENABLED=true \ devlikeapro/waha
curl -X PUT http://localhost:3000/api/session/default \ -H "Content-Type: application/json" \ -d '{ "config": { "webhooks": [ { "url": "https://your-n8n-domain.com/webhook/whatsapp-lead", "events": ["message"] } ] } }'
curl -X PUT http://localhost:3000/api/session/default \ -H "Content-Type: application/json" \ -d '{ "config": { "webhooks": [ { "url": "https://your-n8n-domain.com/webhook/whatsapp-lead", "events": ["message"] } ] } }'
curl -X PUT http://localhost:3000/api/session/default \ -H "Content-Type: application/json" \ -d '{ "config": { "webhooks": [ { "url": "https://your-n8n-domain.com/webhook/whatsapp-lead", "events": ["message"] } ] } }'
{ "event": "message", "session": "default", "payload": { "id": "[email protected]_3EB0A608C4A3", "timestamp": 1710500000, "from": "[email protected]", "body": "Hi, I'm interested in your automation services", "hasMedia": false }
}
{ "event": "message", "session": "default", "payload": { "id": "[email protected]_3EB0A608C4A3", "timestamp": 1710500000, "from": "[email protected]", "body": "Hi, I'm interested in your automation services", "hasMedia": false }
}
{ "event": "message", "session": "default", "payload": { "id": "[email protected]_3EB0A608C4A3", "timestamp": 1710500000, "from": "[email protected]", "body": "Hi, I'm interested in your automation services", "hasMedia": false }
}
{ "phone": "{{ $json.payload.from.replace('@c.us', '') }}", "message": "{{ $json.payload.body }}", "source": "whatsapp", "captured_at": "{{ $now.toISO() }}"
}
{ "phone": "{{ $json.payload.from.replace('@c.us', '') }}", "message": "{{ $json.payload.body }}", "source": "whatsapp", "captured_at": "{{ $now.toISO() }}"
}
{ "phone": "{{ $json.payload.from.replace('@c.us', '') }}", "message": "{{ $json.payload.body }}", "source": "whatsapp", "captured_at": "{{ $now.toISO() }}"
}
INSERT INTO leads (phone, message, source, captured_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (phone) DO UPDATE SET last_message = $2, updated_at = $4;
INSERT INTO leads (phone, message, source, captured_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (phone) DO UPDATE SET last_message = $2, updated_at = $4;
INSERT INTO leads (phone, message, source, captured_at)
VALUES ($1, $2, $3, $4)
ON CONFLICT (phone) DO UPDATE SET last_message = $2, updated_at = $4;
curl -X POST http://localhost:3000/api/sendText \ -H "Content-Type: application/json" \ -d '{ "session": "default", "chatId": "[email protected]", "text": "Thanks for reaching out! We received your message and will get back to you shortly." }'
curl -X POST http://localhost:3000/api/sendText \ -H "Content-Type: application/json" \ -d '{ "session": "default", "chatId": "[email protected]", "text": "Thanks for reaching out! We received your message and will get back to you shortly." }'
curl -X POST http://localhost:3000/api/sendText \ -H "Content-Type: application/json" \ -d '{ "session": "default", "chatId": "[email protected]", "text": "Thanks for reaching out! We received your message and will get back to you shortly." }'
Webhook → IF (keyword filter) → Set (extract data) → DB Insert → HTTP Request (auto-reply)
Webhook → IF (keyword filter) → Set (extract data) → DB Insert → HTTP Request (auto-reply)
Webhook → IF (keyword filter) → Set (extract data) → DB Insert → HTTP Request (auto-reply) - WAHA — an unofficial, open-source WhatsApp API (not affiliated with or endorsed by Meta/WhatsApp). It wraps WhatsApp Web into a REST API.
- n8n — an open-source workflow automation platform you can self-host. - A VPS or local machine with Docker installed
- Basic familiarity with REST APIs and JSON
- A spare phone number for WhatsApp (don't use your personal number for development) - Type: Webhook
- HTTP Method: POST
- Path: whatsapp-lead - Condition: {{ $json.payload.body }} contains keywords like "interested", "pricing", "demo", "info" - Method: POST
- URL: http://waha:3000/api/sendText
- Body: JSON with session, chatId (from the incoming payload), and text - Duplicate messages: WAHA may occasionally deliver the same message twice. Use the message id field as a deduplication key in your database.
- Rate limiting: WhatsApp may flag accounts that send too many messages too quickly. Keep auto-replies minimal and add delays if needed.
- Session persistence: WAHA sessions can expire. Mount a Docker volume (-v waha_data:/app/store) to persist session data across container restarts.
- Media messages: If hasMedia is true, you'll need to call WAHA's media download endpoint separately. For lead capture, text messages are usually sufficient. - Put WAHA behind a reverse proxy (Nginx/Traefik) with HTTPS
- Use n8n's built-in authentication for the webhook (Header Auth or Basic Auth)
- Never expose WAHA's API port directly to the internet without authentication
- Store lead data in compliance with your local privacy regulations - No per-message fees — unlike the official WhatsApp Business API, which charges per conversation
- Full data ownership — lead data never passes through a third-party SaaS
- Customizable — modify the workflow to match your exact business logic
- No vendor lock-in — swap any component without rebuilding everything - Add a conversational flow with multiple steps (ask for name, email, budget)
- Connect to a calendar booking system for automatic demo scheduling
- Build lead scoring based on message content and response patterns
- Add multi-language support by detecting the message language