| name | add-whatsapp |
| description | Add WhatsApp as a channel. Can replace other channels entirely or run alongside them. Uses QR code or pairing code for authentication. |
| disable-model-invocation | true |
Add WhatsApp Channel
This skill adds WhatsApp support to Deus. It builds the local MCP packages, registers the channel, and guides through authentication.
IMPORTANT: Do NOT add git remotes, fetch from external repos, or install npm packages from the public registry during this skill. All channel code is already in the repo under packages/ and src/channels/.
Phase 1: Pre-flight
Recovery: If something goes wrong in Phase 1, simply re-run the skill from the beginning.
Check current state
Check if WhatsApp is already configured. If store/auth/ exists with credential files, skip to Phase 4 (Registration) or Phase 5 (Verify).
ls store/auth/creds.json 2>/dev/null && echo "WhatsApp auth exists" || echo "No WhatsApp auth"
Detect environment
Check whether the environment is headless (no display server):
[[ -z "$DISPLAY" && -z "$WAYLAND_DISPLAY" && "$OSTYPE" != darwin* ]] && echo "IS_HEADLESS=true" || echo "IS_HEADLESS=false"
Ask the user
Use AskUserQuestion to collect configuration. Adapt auth options based on environment:
If IS_HEADLESS=true AND not WSL → AskUserQuestion: How do you want to authenticate WhatsApp?
- Pairing code (Recommended) - Enter a numeric code on your phone (no camera needed, requires phone number)
- QR code in terminal - Displays QR code in the terminal (can be too small on some displays)
Otherwise (macOS, desktop Linux, Windows, or WSL) → AskUserQuestion: How do you want to authenticate WhatsApp?
- QR code in terminal (Recommended) - Displays QR code in the terminal
- Pairing code - Enter a numeric code on your phone (no camera needed, requires phone number)
If they chose pairing code:
AskUserQuestion: What is your phone number? (Include country code without +, e.g., 1234567890)
Phase 2: Build Local Packages
Recovery: If the build fails, fix the error and re-run from Phase 2. No need to redo Phase 1.
The WhatsApp channel is a local MCP server in packages/. Build the packages in order (core first, then whatsapp):
cd packages/mcp-channel-core && npm install && npm run build && cd ../..
cd packages/mcp-whatsapp && npm install && npm run build && cd ../..
Configure environment
Add ASSISTANT_HAS_OWN_NUMBER to .env if not already present:
ASSISTANT_HAS_OWN_NUMBER=false
Validate
npm run build
Build must be clean before proceeding.
Phase 3: Authentication
Recovery: If authentication fails, delete store/auth/ and re-run from Phase 3. Phases 1-2 do not need to be repeated.
Clean previous auth state (if re-authenticating)
rm -rf store/auth/
Run WhatsApp authentication
The auth script is at scripts/whatsapp-auth.ts. It uses baileys from the mcp-whatsapp workspace package.
For QR code in terminal:
npx tsx scripts/whatsapp-auth.ts
(Bash timeout: 120000ms)
Tell the user:
A QR code will appear in the terminal.
- Open WhatsApp > Settings > Linked Devices > Link a Device
- Scan the QR code displayed in the terminal
- Wait for "Authentication successful!" message
If the QR code is cut off or doesn't display: The auth script writes raw QR data to store/qr-data.txt. Render it manually:
node -e "require('qrcode-terminal').generate(require('fs').readFileSync('store/qr-data.txt','utf-8').trim(), {small: true})"
For pairing code:
Tell the user to have WhatsApp open on Settings > Linked Devices > Link a Device, ready to tap "Link with phone number instead" — the code expires in ~60 seconds and must be entered immediately.
Run the auth process in the background and poll store/pairing-code.txt for the code:
rm -f store/pairing-code.txt && npx tsx scripts/whatsapp-auth.ts --pairing-code --phone <their-phone-number> > /tmp/wa-auth.log 2>&1 &
Then immediately poll for the code (do NOT wait for the background command to finish):
for i in $(seq 1 50); do [ -f store/pairing-code.txt ] && cat store/pairing-code.txt && break; sleep 0.2; done
Display the code to the user the moment it appears. Tell them:
Enter this code now — it expires in ~60 seconds.
- Open WhatsApp > Settings > Linked Devices > Link a Device
- Tap Link with phone number instead
- Enter the code immediately
After the user enters the code, poll for authentication to complete:
for i in $(seq 1 120); do grep -q 'AUTH_STATUS: authenticated' /tmp/wa-auth.log 2>/dev/null && echo "authenticated" && break; grep -q 'AUTH_STATUS: failed' /tmp/wa-auth.log 2>/dev/null && echo "failed" && break; sleep 0.5; done
If failed: qr_timeout → re-run. logged_out → delete store/auth/ and re-run. 515 → re-run. timeout → ask user, offer retry.
Verify authentication succeeded
test -f store/auth/creds.json && echo "Authentication successful" || echo "Authentication failed"
Configure environment
Channels auto-enable when their credentials are present — WhatsApp activates when store/auth/creds.json exists.
Sync to container environment:
mkdir -p data/env && cp .env data/env/env
Phase 4: Registration
Recovery: If registration fails or you chose the wrong group, re-run from Phase 4. Authentication (Phase 3) is preserved.
Configure trigger and channel type
Get the bot's WhatsApp number: node -e "const c=require('./store/auth/creds.json');console.log(c.me.id.split(':')[0].split('@')[0])"
AskUserQuestion: Is this a shared phone number (personal WhatsApp) or a dedicated number (separate device)?
- Shared number - Your personal WhatsApp number (recommended: use self-chat or a solo group)
- Dedicated number - A separate phone/SIM for the assistant
AskUserQuestion: What trigger word should activate the assistant?
- @Deus - Default trigger
- @Claw - Short and easy
- @Claude - Match the AI name
AskUserQuestion: What should the assistant call itself?
- Deus - Default name
- Claw - Short and easy
- Claude - Match the AI name
AskUserQuestion: Where do you want to chat with the assistant?
Shared number options:
- Self-chat (Recommended) - Chat in your own "Message Yourself" conversation
- Solo group - A group with just you and the linked device
- Existing group - An existing WhatsApp group
Dedicated number options:
- DM with bot (Recommended) - Direct message the bot's number
- Solo group - A group with just you and the bot
- Existing group - An existing WhatsApp group
Get the JID
Self-chat: JID = your phone number with @s.whatsapp.net. Extract from auth credentials:
node -e "const c=JSON.parse(require('fs').readFileSync('store/auth/creds.json','utf-8'));console.log(c.me?.id?.split(':')[0]+'@s.whatsapp.net')"
DM with bot: Ask for the bot's phone number. JID = NUMBER@s.whatsapp.net
Group (solo, existing): Run group sync first:
npx tsx setup/index.ts --step groups
Then use a search-based picker (group lists can have 300+ entries):
-
AskUserQuestion: Type the name (or part of the name) of the WhatsApp group you want to register.
-
Filter groups using the search term:
npx tsx setup/index.ts --step groups --list | grep -i "<search-term>" | head -10
-
Present the top 10 matching groups as AskUserQuestion options (names only, not JIDs). Include two extra options at the bottom:
- Show more results — re-run the filter with
head -20 to show the next batch
- Search again — ask for a different search term and repeat from step 1
-
Once the user picks a group, extract the JID from the matching line.
Register the chat
npx tsx setup/index.ts --step register \
--jid "<jid>" \
--name "<chat-name>" \
--trigger "@<trigger>" \
--folder "whatsapp_main" \
--channel whatsapp \
--assistant-name "<name>" \
--is-main \
--no-trigger-required
For additional groups (trigger-required):
npx tsx setup/index.ts --step register \
--jid "<group-jid>" \
--name "<group-name>" \
--trigger "@<trigger>" \
--folder "whatsapp_<group-name>" \
--channel whatsapp
Phase 5: Verify
Recovery: If verification fails, check logs (tail -50 logs/deus.log). For service issues, re-run from Phase 5. For auth issues, re-run from Phase 3. For registration issues, re-run from Phase 4.
Build and restart
npm run build
Restart the service:
launchctl kickstart -k gui/$(id -u)/com.deus
systemctl --user restart deus
nssm restart deus
Test the connection
Tell the user:
Send a message to your registered WhatsApp chat:
- For self-chat / main: Any message works
- For groups: Use the trigger word (e.g., "@Deus hello")
The assistant should respond within a few seconds.
Smoke test
Run the automated smoke test to verify service, DB, and channel connection:
npx tsx setup/index.ts --step smoke-test -- --channel whatsapp
The smoke test checks: service running, registered group exists, DB write/read works, and channel connection appears in logs.
If the smoke test passes, tell the user "WhatsApp channel is working."
If it fails, check the STATUS output for the specific failure (service down, no registered group, DB error, or no log connection). Guide the user to fix the issue before proceeding.
After the automated check, also ask the user to send a test message to verify real-time delivery:
Send a message to your registered WhatsApp chat to confirm real-time delivery.
- For self-chat / main: Any message works
- For groups: Use the trigger word (e.g., "@Deus hello")
Check logs if needed
tail -f logs/deus.log
Troubleshooting
QR code expired
QR codes expire after ~60 seconds. Re-run the auth script:
rm -rf store/auth/ && npx tsx scripts/whatsapp-auth.ts
Pairing code not working
Codes expire in ~60 seconds. To retry:
rm -rf store/auth/ && npx tsx scripts/whatsapp-auth.ts --pairing-code --phone <phone>
Enter the code immediately when it appears. Also ensure:
- Phone number includes country code without
+ (e.g., 1234567890)
- Phone has internet access
- WhatsApp is updated to the latest version
"conflict" disconnection
This happens when two instances connect with the same credentials. Ensure only one Deus process is running:
pkill -f "node dist/index.js"
Bot not responding
Check:
- Auth credentials exist:
ls store/auth/creds.json
- Chat is registered:
sqlite3 store/messages.db "SELECT * FROM registered_groups WHERE jid LIKE '%whatsapp%' OR jid LIKE '%@g.us' OR jid LIKE '%@s.whatsapp.net'"
- Service is running:
launchctl list | grep deus (macOS) or systemctl --user status deus (Linux)
- Logs:
tail -50 logs/deus.log
Group names not showing
Run group metadata sync:
npx tsx setup/index.ts --step groups
This fetches all group names from WhatsApp. Runs automatically every 24 hours.
After Setup
If running npm run dev while the service is active:
launchctl unload ~/Library/LaunchAgents/com.deus.plist
npm run dev
launchctl load ~/Library/LaunchAgents/com.deus.plist
Troubleshooting: Re-authentication
If WhatsApp disconnects (session revoked, "logged out" error in logs, or store/auth/creds.json becomes invalid):
- Delete the auth state:
rm -rf store/auth/
- Re-run Phase 3 (Authentication) — Phases 1-2 do not need to be repeated
- After re-authentication, restart the service — no re-registration needed (the JID stays the same)
Common causes of disconnection:
- Linked the same WhatsApp account to another instance of Deus or another Baileys client
- Manually removed the linked device from WhatsApp Settings > Linked Devices
- WhatsApp server-side session expiry (rare, ~14 days of inactivity)
Removal
To remove WhatsApp integration:
- Delete auth credentials:
rm -rf store/auth/
- Remove WhatsApp registrations:
sqlite3 store/messages.db "DELETE FROM registered_groups WHERE jid LIKE '%@g.us' OR jid LIKE '%@s.whatsapp.net'"
- Remove import from
src/channels/index.ts
- Sync env:
mkdir -p data/env && cp .env data/env/env
- Rebuild and restart