Skip to main content
WhatsApp is a messaging channel for NanoClaw, installed via the /add-whatsapp skill. It uses the Baileys library and merges the skill/whatsapp branch into your fork.

Overview

NanoClaw connects to WhatsApp using the Baileys library, which provides a clean interface to WhatsApp’s Web API.

Features

  • Group and individual chats - Message the assistant in any WhatsApp chat
  • Multi-device support - Uses WhatsApp’s official multi-device protocol
  • Automatic reconnection - Handles disconnections gracefully
  • Typing indicators - Shows when the assistant is working
  • Message queuing - Queues messages when disconnected and flushes on reconnect
  • LID translation - Handles WhatsApp’s Locally IDentified (LID) JID format
  • Group metadata sync - Automatically syncs group names every 24 hours

How it works

The WhatsApp channel implementation is in src/channels/whatsapp.ts:
export class WhatsAppChannel implements Channel {
  name = 'whatsapp';
  
  async connect(): Promise<void> {
    // Connects to WhatsApp using Baileys
  }
  
  async sendMessage(jid: string, text: string): Promise<void> {
    // Sends messages with assistant name prefix
  }
  
  async setTyping(jid: string, isTyping: boolean): Promise<void> {
    // Shows typing indicator
  }
}

Architecture

WhatsApp (Baileys) → SQLite → Polling loop → Container (Claude Agent SDK) → Response
1

Authentication

WhatsApp Web QR code authentication. Credentials stored in store/auth/.
2

Message receipt

Baileys emits messages.upsert events, filtered by registered groups.
3

Message storage

Messages are stored in SQLite (store/messages.db).
4

Queue processing

The polling loop checks for messages that require agent responses.
5

Container invocation

Agent runs in an isolated Linux container with the group’s filesystem mounted.
6

Response routing

Response is sent back via sendMessage() with assistant name prefix.

Setup

WhatsApp is not bundled with NanoClaw’s core install. Add it using the /add-whatsapp skill:
claude
/add-whatsapp
This merges the skill/whatsapp branch into your fork, adding the WhatsApp channel code and its dependencies (including @whiskeysockets/baileys). The skill will then:
  1. Generate a QR code for WhatsApp Web authentication
  2. Wait for you to scan it with your phone
  3. Store authentication credentials in store/auth/
  4. Connect to WhatsApp and verify the connection
If authentication fails or expires, run /add-whatsapp again to re-authenticate.

Configuration

Assistant has own number

Set this in src/config.ts:
export const ASSISTANT_HAS_OWN_NUMBER = false; // Default: shared number
Shared number (false):
  • You and the assistant share the same WhatsApp number
  • Assistant messages are prefixed with the assistant name (e.g., “Andy: Hello!”)
  • Required for self-chat (messaging yourself)
Own number (true):
  • The assistant has its own dedicated WhatsApp number
  • No message prefix needed
  • Cleaner appearance in group chats

Assistant name

The assistant name is used as the message prefix and trigger pattern:
export const ASSISTANT_NAME = 'Andy';
Messages from the assistant appear as:
Andy: Your scheduled task completed successfully.

Connection settings

Connection logic is in src/channels/whatsapp.ts:
const { version } = await fetchLatestWaWebVersion({});
this.sock = makeWASocket({
  version,
  auth: { creds: state.creds, keys: makeCacheableSignalKeyStore(state.keys, logger) },
  printQRInTerminal: false,
  logger,
  browser: Browsers.macOS('Chrome'),
});

Message handling

Bot message detection

The channel determines if a message came from the bot:
const isBotMessage = ASSISTANT_HAS_OWN_NUMBER
  ? fromMe
  : content.startsWith(`${ASSISTANT_NAME}:`);
This prevents the bot from responding to its own messages.

Message prefix

Outgoing messages are prefixed unless using a dedicated number:
const prefixed = ASSISTANT_HAS_OWN_NUMBER
  ? text
  : `${ASSISTANT_NAME}: ${text}`;

Group metadata sync

Group names are synced from WhatsApp every 24 hours:
async syncGroupMetadata(force = false): Promise<void> {
  const groups = await this.sock.groupFetchAllParticipating();
  for (const [jid, metadata] of Object.entries(groups)) {
    if (metadata.subject) {
      updateChatName(jid, metadata.subject);
    }
  }
}
This ensures group names in the database match their current WhatsApp names.

JID format

WhatsApp uses JIDs (Jabber IDs) to identify chats:
  • Individual: 1234567890@s.whatsapp.net
  • Group: 120363012345678901@g.us
  • LID (newer format): 123456:78@lid
The channel translates LID JIDs to phone JIDs automatically.

Registering chats

To make the assistant respond in a chat, register it:

Main chat (self-chat)

Your self-chat is the admin control channel:
registerGroup("<your-number>@s.whatsapp.net", {
  name: "Main",
  folder: "main",
  trigger: `@${ASSISTANT_NAME}`,
  added_at: new Date().toISOString(),
  requiresTrigger: false,
});

Group chats

For WhatsApp groups:
registerGroup("<group-jid>@g.us", {
  name: "Family Chat",
  folder: "family-chat",
  trigger: `@${ASSISTANT_NAME}`,
  added_at: new Date().toISOString(),
  requiresTrigger: true, // Only responds when mentioned
});
Use requiresTrigger: true for group chats so the assistant only responds when explicitly mentioned.

WhatsApp-specific skills

Several skills extend WhatsApp with media handling capabilities. These live on the nanoclaw-whatsapp fork as skill/* branches.
SkillCommandWhat it does
Voice transcription/add-voice-transcriptionTranscribe voice notes via Whisper API
Local whisper/use-local-whisperOffline transcription via whisper.cpp
Image vision/add-image-visionUnderstand image attachments
PDF reader/add-pdf-readerExtract text from PDF attachments
Emoji reactions/add-reactionsSend, receive, and search emoji reactions
See voice transcription, image vision, and PDF reader for detailed documentation.

Emoji reactions

The /add-reactions skill adds full emoji reaction support:
  • Receive reactions: Reactions on messages are stored in a dedicated reactions table with a forward-only emoji state machine (sent → delivered → read → reacted)
  • Send reactions: A react_to_message MCP tool lets the agent react to specific messages
  • Search reactions: Query reactions by emoji, sender, or message
  • Database migration: Adds a reactions table automatically on first run
# Install on your nanoclaw-whatsapp fork
git fetch whatsapp skill/add-reactions
git merge whatsapp/skill/add-reactions

Troubleshooting

Authentication expired

If you see “WhatsApp authentication required” notifications:
claude
/setup
Follow the QR code authentication flow again.

Connection issues

The channel automatically reconnects on disconnection:
if (connection === 'close') {
  const shouldReconnect = reason !== DisconnectReason.loggedOut;
  if (shouldReconnect) {
    logger.info('Reconnecting...');
    this.connectInternal().catch(...);
  }
}
Messages are queued during disconnection and flushed on reconnect.

Not receiving messages

Check if the chat is registered:
sqlite3 store/messages.db "SELECT * FROM registered_groups"
If the chat isn’t listed, register it using the process above.

Bot responding to itself

Ensure ASSISTANT_HAS_OWN_NUMBER is set correctly in src/config.ts:
  • If sharing a number, set to false (bot messages will have the name prefix)
  • If using a dedicated number, set to true (bot messages are identified by fromMe)

Using other channels

NanoClaw supports multiple messaging channels as equal options. You can run WhatsApp alongside other channels or use a different channel entirely.

Add another channel

/add-telegram  # Adds Telegram alongside WhatsApp

Remove WhatsApp

To remove the WhatsApp skill and revert to a channel-free core:
git revert -m 1 <whatsapp-merge-commit>
See removing a skill for details.

Implementation details

Message queue

Messages sent while disconnected are queued and flushed on reconnect:
private async flushOutgoingQueue(): Promise<void> {
  while (this.outgoingQueue.length > 0) {
    const item = this.outgoingQueue.shift()!;
    await this.sock.sendMessage(item.jid, { text: item.text });
  }
}

Presence updates

The channel announces availability on connection:
this.sock.sendPresenceUpdate('available').catch(...);
And sends typing indicators per chat:
async setTyping(jid: string, isTyping: boolean): Promise<void> {
  const status = isTyping ? 'composing' : 'paused';
  await this.sock.sendPresenceUpdate(status, jid);
}

Source code reference

After installing the WhatsApp skill, the implementation lives in:
  • src/channels/whatsapp.ts - Main WhatsAppChannel class
  • src/config.ts - Configuration constants (extended by the skill)
  • src/index.ts - Channel initialization and routing
These files are added by the skill/whatsapp branch merge. They do not exist in NanoClaw’s core install.

Next steps

Add Telegram

Add Telegram support alongside or instead of WhatsApp

Add Discord

Add Discord support to your installation
Last modified on March 19, 2026