WhatsApp CLI - Complete Reference

August 8, 2026 · View on GitHub

For Humans & LLMs: This document covers the WhatsApp CLI tool: installation, usage, the API reference, examples, architecture, and troubleshooting. Humans and large language models can both read it.

Version: 1.3.2 Repository: https://github.com/vicentereig/whatsapp-cli License: MIT Language: Go 1.24+


Table of Contents


Overview

What is WhatsApp CLI?

WhatsApp CLI is a standalone command-line tool for WhatsApp. It uses the WhatsApp Web multidevice protocol. Every command returns structured JSON output. Use it for:

  • Automation: Shell scripts, cron jobs, CI/CD pipelines
  • AI Integration: Codex, Claude Code, GPT-based tools
  • Data Analysis: Extract and analyze WhatsApp conversations
  • Custom Applications: Build tools on top of WhatsApp

Key Features

FeatureDescription
Zero DependenciesSingle compiled binary (21MB), no runtime required
JSON OutputAll commands return structured JSON for easy parsing
Persistent SessionsAuthenticate once via QR code, auto-reconnect for ~20 days
Local StorageSQLite database, no cloud dependencies
Full MessagingSend, receive, search messages; manage contacts & chats
Group SupportSend/receive messages in group chats
TDD Implementation100% test coverage, production-ready

System Requirements

  • Operating System: Linux, macOS, Windows
  • Architecture: x86_64, ARM64
  • Go Version: 1.24+ (for building from source)
  • Storage: ~50MB for binary + variable for message database
  • Network: Internet connection required
  • WhatsApp Account: Active WhatsApp account with smartphone

Installation

brew install vicentereig/tap/whatsapp-cli

Or tap first, then install:

brew tap vicentereig/tap
brew install whatsapp-cli

Method 2: Download Pre-built Binary

# Linux (x86_64)
curl -LO https://github.com/vicentereig/whatsapp-cli/releases/latest/download/whatsapp-cli-linux-amd64.tar.gz
tar -xzf whatsapp-cli-linux-amd64.tar.gz
sudo mv whatsapp-cli-linux-amd64 /usr/local/bin/whatsapp-cli

# macOS (ARM64 - M1/M2/M3)
curl -LO https://github.com/vicentereig/whatsapp-cli/releases/latest/download/whatsapp-cli-darwin-arm64.tar.gz
tar -xzf whatsapp-cli-darwin-arm64.tar.gz
sudo mv whatsapp-cli-darwin-arm64 /usr/local/bin/whatsapp-cli

# macOS (Intel)
curl -LO https://github.com/vicentereig/whatsapp-cli/releases/latest/download/whatsapp-cli-darwin-amd64.tar.gz
tar -xzf whatsapp-cli-darwin-amd64.tar.gz
sudo mv whatsapp-cli-darwin-amd64 /usr/local/bin/whatsapp-cli

# Windows (x86_64) - download and extract whatsapp-cli-windows-amd64.zip from releases page

Method 3: Build from Source

# Clone repository
git clone https://github.com/vicentereig/whatsapp-cli.git
cd whatsapp-cli

# Install dependencies
go mod download

# Build
go build -o whatsapp-cli .

# Install (optional)
sudo mv whatsapp-cli /usr/local/bin/

# Verify installation
whatsapp-cli --help

Method 4: Install via Go

go install github.com/vicentereig/whatsapp-cli@latest

Release & Distribution Notes

  • Version tags use semantic versioning (vMAJOR.MINOR.PATCH). For reproducible builds, use a specific tag, for example v1.0.0, with go install github.com/vicentereig/whatsapp-cli@v1.0.0.
  • The GitHub Releases page has pre-built artifacts for Linux, macOS, and Windows. Each archive has a matching SHA-256 entry in checksums.txt. Before you install, run shasum -a 256 -c checksums.txt --ignore-missing to verify the archive.
  • Binaries use the name whatsapp-cli-<os>-<arch> (Windows adds .exe). After you extract the file, mark it executable with chmod +x and place it in your PATH, for example /usr/local/bin/whatsapp-cli.
  • Each uploaded file, including checksums.txt, also has a Sigstore cosign signature (.sig) and certificate (.pem). To verify a file, run cosign verify-blob --certificate <file>.pem --signature <file>.sig <file>. GitHub Actions uses OIDC identities, so you can enforce provenance during verification.
  • To build from source, follow Method 3. Use this method to audit the code, change compilation flags, or test changes before you tag a release.
  • Maintainers can follow the steps in docs/RELEASE.md. It covers go install, source builds, and GitHub Release automation.

Quick Start

Step 1: First-Time Authentication

whatsapp-cli auth

What happens:

  1. QR code appears in terminal
  2. Open WhatsApp on your phone → Settings → Linked Devices → Link a Device
  3. Scan the QR code
  4. Session saved to ./store/whatsapp.db

Output:

{
  "success": true,
  "data": {
    "authenticated": true,
    "message": "Successfully authenticated"
  },
  "error": null
}

Session Duration: ~20 days before re-authentication required

Step 2: Sync Messages

Before you list or search messages, sync them from WhatsApp:

# Start syncing messages (run this in the background or a separate terminal)
whatsapp-cli sync
# Press Ctrl+C when done syncing

What happens:

  1. Connects to WhatsApp and stays connected
  2. Downloads message history from WhatsApp servers
  3. Receives new messages in real-time
  4. Stores everything in ./store/messages.db
  5. Runs until you press Ctrl+C

Tip: Run sync in a tmux or screen session, or as a background service, so it keeps receiving messages.

Step 3: Basic Operations

# List your chats
whatsapp-cli chats list --limit 10

# Search for a contact
whatsapp-cli contacts search --query "John"

# Send a message
whatsapp-cli send --to 1234567890 --message "Hello from CLI!"

# Search messages
whatsapp-cli messages search --query "meeting"

Step 4: Check the Installed Version

whatsapp-cli version

Example Output:

{
  "success": true,
  "data": {
    "version": "v1.1.0"
  },
  "error": null
}

Complete Command Reference

Global Options

All commands support these global flags:

FlagTypeDefaultDescription
--storestring./storeDirectory for session and message databases

Example:

whatsapp-cli --store /var/lib/whatsapp chats list

Command: auth

Authenticate with WhatsApp via QR code.

Syntax:

whatsapp-cli auth

Parameters: None

Returns:

{
  "success": true,
  "data": {
    "authenticated": boolean,
    "message": string
  },
  "error": null
}

Behavior:

  • If already authenticated: Returns success immediately
  • If not authenticated: Displays QR code and waits for scan
  • Timeout: 5 minutes
  • Creates store/whatsapp.db with session data

Example:

whatsapp-cli auth
# Scan QR code with phone
# ✓ Successfully authenticated!

Command: sync

⚠️ IMPORTANT: Run this command to fill the message database. If you do not run sync, messages list and messages search return empty results.

Sync messages from WhatsApp to the local database continuously. This command:

  • Downloads message history from WhatsApp servers
  • Receives new messages in real time
  • Stores all messages in the SQLite database
  • Runs until you press Ctrl+C

Syntax:

whatsapp-cli sync

Parameters: None

Returns: (on exit via Ctrl+C)

{
  "success": true,
  "data": {
    "synced": true,
    "messages_count": 1234
  },
  "error": null
}

Behavior:

  • Connects to WhatsApp (authenticates if needed)
  • Registers event handlers for incoming messages and history sync
  • Processes *events.Message for real-time messages
  • Processes *events.HistorySync for message history batches
  • Stores all messages in store/messages.db
  • Sends progress to stderr (does not affect JSON output)
  • Runs until interrupted (Ctrl+C)
  • Disconnects on exit

Progress Output (stderr):

🚀 Starting WhatsApp sync...
✓ Connected to WhatsApp
🔄 Listening for messages... (Press Ctrl+C to stop)
📜 Processing history sync (42 conversations)...
💬 Synced 1234 messages...
^C
✓ Sync completed. Total messages synced: 1234

Examples:

# Basic sync - run in foreground
whatsapp-cli sync

# Run in background (recommended for continuous syncing)
whatsapp-cli sync > sync.json 2> sync.log &

# Run in tmux/screen session
tmux new -s whatsapp
whatsapp-cli sync
# Detach with Ctrl+B D

# Run with custom storage directory
whatsapp-cli --store /var/lib/whatsapp sync

# Stop sync gracefully
kill -INT <pid>
# Or press Ctrl+C in foreground

Use Cases:

  1. Initial Setup: Run once to download all message history
  2. Continuous Sync: Run as background service to receive messages
  3. Periodic Sync: Run via cron to update messages on a schedule
  4. Development: Run in terminal while testing queries

Notes:

  • Message history sync can take time. The duration depends on the number of messages.
  • SQLite PRIMARY KEY constraints prevent duplicate messages.
  • The CLI does not download media files. It stores only metadata: type, filename, and URL.
  • The connection stays open until you interrupt it.
  • You can restart the sync safely. It does not create duplicate messages.

Command: messages list

List messages from all chats or a specific chat.

Syntax:

whatsapp-cli messages list [OPTIONS]

Parameters:

FlagTypeRequiredDefaultDescription
--chatstringNo-Filter by chat JID (e.g., 1234567890@s.whatsapp.net)
--limitintNo20Maximum number of messages to return
--pageintNo0Page number for pagination (0-indexed)

Returns:

{
  "success": true,
  "data": [
    {
      "id": "msg_unique_id",
      "chat_jid": "1234567890@s.whatsapp.net",
      "chat_name": "John Doe",
      "sender": "1234567890",
      "content": "Message text content",
      "timestamp": "2025-10-26T10:30:00Z",
      "is_from_me": false,
      "media_type": ""
    }
  ],
  "error": null
}

Examples:

# List 50 most recent messages across all chats
whatsapp-cli messages list --limit 50

# List messages from specific chat
whatsapp-cli messages list --chat 1234567890@s.whatsapp.net

# Pagination: Get second page of results
whatsapp-cli messages list --limit 20 --page 1

# Get JID first, then list messages
JID=$(whatsapp-cli contacts search --query "Alice" | jq -r '.data[0].jid')
whatsapp-cli messages list --chat "$JID" --limit 100

Sorting: Messages returned in reverse chronological order (newest first)


Search messages by content across all chats.

Syntax:

whatsapp-cli messages search --query TEXT [OPTIONS]

Parameters:

FlagTypeRequiredDefaultDescription
--querystringYes-Search term (case-insensitive, partial match)
--limitintNo20Maximum number of results
--pageintNo0Page number for pagination

Returns: Same format as messages list

Examples:

# Search for messages containing "meeting"
whatsapp-cli messages search --query "meeting"

# Search with more results
whatsapp-cli messages search --query "project" --limit 100

# Case-insensitive search
whatsapp-cli messages search --query "URGENT"  # Finds "urgent", "Urgent", etc.

Search Behavior:

  • Case-insensitive
  • Partial word matching
  • Searches message content only (not sender names)
  • Returns messages from all chats

Search contacts by name or phone number.

Syntax:

whatsapp-cli contacts search --query TEXT

Parameters:

FlagTypeRequiredDefaultDescription
--querystringYes-Search term for name or phone number

Returns:

{
  "success": true,
  "data": [
    {
      "phone_number": "1234567890",
      "name": "John Doe",
      "jid": "1234567890@s.whatsapp.net"
    }
  ],
  "error": null
}

Examples:

# Search by name
whatsapp-cli contacts search --query "John"

# Search by partial phone number
whatsapp-cli contacts search --query "5551234"

# Extract JID for further operations
JID=$(whatsapp-cli contacts search --query "Alice" | jq -r '.data[0].jid')
echo "Alice's JID: $JID"

Behavior:

  • Returns maximum 50 results
  • Excludes group chats (only individual contacts)
  • Sorted alphabetically by name
  • Partial matching on both name and JID

Command: chats list

List all chats sorted by recent activity.

Syntax:

whatsapp-cli chats list [OPTIONS]

Parameters:

FlagTypeRequiredDefaultDescription
--querystringNo-Filter chats by name or JID
--limitintNo20Maximum number of chats
--pageintNo0Page number for pagination

Returns:

{
  "success": true,
  "data": [
    {
      "jid": "1234567890@s.whatsapp.net",
      "name": "John Doe",
      "last_message_time": "2025-10-26T10:30:00Z"
    }
  ],
  "error": null
}

Examples:

# List 20 most recent chats
whatsapp-cli chats list

# List all chats (with pagination)
whatsapp-cli chats list --limit 100

# Filter chats by name
whatsapp-cli chats list --query "Team"

# Get list of all group chats
whatsapp-cli chats list | jq '.data[] | select(.jid | endswith("@g.us"))'

Sorting: Chats ordered by last_message_time (most recent first)

Chat Types:

  • Individual chats: JID ends with @s.whatsapp.net
  • Group chats: JID ends with @g.us

Command: send

Send a text message to an individual or group.

Syntax:

whatsapp-cli send --to RECIPIENT --message TEXT

Parameters:

FlagTypeRequiredDefaultDescription
--tostringYes-Phone number or JID
--messagestringYes-Message text content

Recipient Formats:

FormatExampleUse Case
Phone number1234567890Individual chats (auto-converted to JID)
Individual JID1234567890@s.whatsapp.netIndividual chats
Group JID123456789@g.usGroup chats (must use JID)

Returns:

{
  "success": true,
  "data": {
    "sent": true,
    "recipient": "1234567890",
    "message": "Hello!"
  },
  "error": null
}

Examples:

# Send to individual (phone number)
whatsapp-cli send --to 1234567890 --message "Hello from CLI!"

# Send to individual (full JID)
whatsapp-cli send --to 1234567890@s.whatsapp.net --message "Hi there!"

# Send to group (requires JID)
whatsapp-cli send --to 123456789@g.us --message "Hello everyone!"

# Send with special characters (use quotes)
whatsapp-cli send --to 1234567890 --message "It's working! 🎉"

# Multi-line messages
whatsapp-cli send --to 1234567890 --message "Line 1
Line 2
Line 3"

# Send result of command
whatsapp-cli send --to 1234567890 --message "Server status: $(uptime)"

Behavior:

  • Requires active connection (authenticates if needed)
  • Message stored locally in database
  • Returns immediately after sending (does not wait for delivery)
  • Supports Unicode (emojis, international characters)

Limitations:

  • The send command supports text messages only. To download attachments, use media download.
  • No delivery/read receipt information returned
  • Maximum message length: WhatsApp's standard limit (~65,536 characters)

Command: media download

Download media attachments (images, videos, audio, documents) that sync stored in the local database.

Syntax:

whatsapp-cli media download --message-id ID [--chat JID] [--output PATH]

Parameters:

FlagTypeRequiredDescription
--message-idstringYesMessage identifier from messages list/search
--chatstringNoChat JID to disambiguate duplicate message IDs
--outputstringNoDestination file or directory (defaults to auto-structured path)

Default storage:

  • The CLI stores media next to the SQLite databases, under STORE/media/{chat}/{message}/{media_type}/filename
  • The CLI sanitizes paths automatically
  • If --output points to a directory, the CLI uses the original filename (or a name based on the message ID)

Return value:

{
  "success": true,
  "data": {
    "message_id": "ABCD1234",
    "chat_jid": "1234567890@s.whatsapp.net",
    "path": "/path/to/media/1234567890@s.whatsapp.net/ABCD1234/image/ABCD1234.jpg",
    "bytes": 204800,
    "media_type": "image",
    "mime_type": "image/jpeg",
    "downloaded_at": "2025-02-01T12:34:56.789Z"
  },
  "error": null
}

Examples:

# Download image using auto-organised directory layout
whatsapp-cli media download --message-id ABCD1234

# Save into a specific directory (filename auto-generated)
whatsapp-cli media download --message-id ABCD1234 --output /tmp/media/

# Save using explicit path and disambiguate by chat JID
whatsapp-cli media download --message-id XYZ987 --chat 1234567890@s.whatsapp.net --output ~/Downloads/report.pdf

Notes:

  • Requires that whatsapp-cli sync captured the message metadata first
  • The sync loop downloads media in the background, without blocking new messages
  • If you run the command again, it overwrites the existing file with a fresh download
  • Errors include metadata issues (an expired link or a missing direct path) or file permission problems

JSON Response Format

All commands return JSON in this format:

Success Response

{
  "success": true,
  "data": <result_data>,
  "error": null
}

Error Response

{
  "success": false,
  "data": null,
  "error": "Error message describing what went wrong"
}

Data Types by Command

CommandData TypeStructure
authobject{"authenticated": bool, "message": string}
messages listarray[Message, ...]
messages searcharray[Message, ...]
contacts searcharray[Contact, ...]
chats listarray[Chat, ...]
sendobject{"sent": bool, "recipient": string, "message": string}

Usage Examples

Example 1: Send Daily Report via Cron

#!/bin/bash
# File: /usr/local/bin/daily-report.sh

RECIPIENT="1234567890"  # Your phone number

# Generate report
REPORT=$(cat <<EOF
📊 Daily Report - $(date +%Y-%m-%d)

Server Status: $(systemctl is-active nginx)
Disk Usage: $(df -h / | awk 'NR==2 {print \$5}')
Memory: $(free -h | awk 'NR==2 {print \$3 "/" \$2}')
Uptime: $(uptime -p)
EOF
)

# Send via WhatsApp
whatsapp-cli send --to "$RECIPIENT" --message "$REPORT"

Cron entry:

0 9 * * * /usr/local/bin/daily-report.sh

Example 2: Search and Bulk Message

#!/bin/bash
# Send message to all contacts matching "Team"

CONTACTS=$(whatsapp-cli contacts search --query "Team" | jq -r '.data[].jid')

for JID in $CONTACTS; do
  whatsapp-cli send --to "$JID" --message "Team meeting at 3 PM today!"
  sleep 2  # Rate limiting
done

Example 3: Export Chat History

#!/bin/bash
# Export specific chat to JSON file

JID="1234567890@s.whatsapp.net"
OUTPUT_FILE="chat_export_$(date +%Y%m%d).json"

# Get all messages (paginated)
PAGE=0
ALL_MESSAGES=[]

while true; do
  RESPONSE=$(whatsapp-cli messages list --chat "$JID" --limit 100 --page $PAGE)
  MESSAGES=$(echo "$RESPONSE" | jq '.data')
  COUNT=$(echo "$MESSAGES" | jq 'length')

  if [ "$COUNT" -eq 0 ]; then
    break
  fi

  ALL_MESSAGES=$(echo "$ALL_MESSAGES" | jq ". + $MESSAGES")
  PAGE=$((PAGE + 1))
done

echo "$ALL_MESSAGES" | jq '.' > "$OUTPUT_FILE"
echo "Exported to $OUTPUT_FILE"

Example 4: Monitor for Keywords

#!/bin/bash
# Alert when specific keywords appear in messages

ALERT_RECIPIENT="your_phone@s.whatsapp.net"
LAST_CHECK_FILE="/tmp/whatsapp_last_check"

# Get timestamp of last check
if [ -f "$LAST_CHECK_FILE" ]; then
  LAST_CHECK=$(cat "$LAST_CHECK_FILE")
else
  LAST_CHECK=$(date -u -d "1 hour ago" +%Y-%m-%dT%H:%M:%SZ)
fi

# Search for urgent messages since last check
MESSAGES=$(whatsapp-cli messages search --query "URGENT" --limit 100 | \
  jq --arg since "$LAST_CHECK" '.data[] | select(.timestamp > $since)')

if [ -n "$MESSAGES" ]; then
  COUNT=$(echo "$MESSAGES" | jq -s 'length')
  whatsapp-cli send --to "$ALERT_RECIPIENT" \
    --message "⚠️ $COUNT urgent messages found!"
fi

# Update last check timestamp
date -u +%Y-%m-%dT%H:%M:%SZ > "$LAST_CHECK_FILE"

Integration with LLMs/AI Tools

Parsing JSON with jq

# Extract specific fields
whatsapp-cli contacts search --query "John" | jq '.data[0].jid'
# Output: "1234567890@s.whatsapp.net"

# Count results
whatsapp-cli chats list | jq '.data | length'
# Output: 42

# Filter and transform
whatsapp-cli messages list | jq '[.data[] | {name: .chat_name, msg: .content}]'

Python Integration

#!/usr/bin/env python3
import subprocess
import json

def whatsapp_cli(command):
    """Execute whatsapp-cli command and return parsed JSON."""
    result = subprocess.run(
        ['whatsapp-cli'] + command.split(),
        capture_output=True,
        text=True
    )
    return json.loads(result.stdout)

# Search contacts
contacts = whatsapp_cli('contacts search --query "Team"')
if contacts['success']:
    for contact in contacts['data']:
        print(f"{contact['name']}: {contact['jid']}")

# Send message
result = whatsapp_cli('send --to 1234567890 --message "Hello from Python!"')
print(f"Message sent: {result['success']}")

Node.js/TypeScript Integration

import { execSync } from 'child_process';

interface WhatsAppResponse<T> {
  success: boolean;
  data: T | null;
  error: string | null;
}

function whatsappCli<T>(command: string): WhatsAppResponse<T> {
  const output = execSync(`whatsapp-cli ${command}`).toString();
  return JSON.parse(output);
}

// Usage
const contacts = whatsappCli<Contact[]>('contacts search --query "Alice"');
if (contacts.success && contacts.data) {
  const jid = contacts.data[0].jid;
  whatsappCli(`send --to ${jid} --message "Hi from Node!"`);
}

Claude Code / Codex Integration

// Example Claude Code MCP tool wrapper

import { execSync } from 'child_process';

export const whatsappTools = {
  sendMessage: async (to: string, message: string) => {
    const result = execSync(
      `whatsapp-cli send --to "${to}" --message "${message}"`
    ).toString();
    return JSON.parse(result);
  },

  searchContacts: async (query: string) => {
    const result = execSync(
      `whatsapp-cli contacts search --query "${query}"`
    ).toString();
    return JSON.parse(result);
  },

  getRecentMessages: async (chatJid: string, limit = 20) => {
    const result = execSync(
      `whatsapp-cli messages list --chat "${chatJid}" --limit ${limit}`
    ).toString();
    return JSON.parse(result);
  }
};

// Use in Claude Code
const contacts = await whatsappTools.searchContacts("Alice");
if (contacts.success) {
  await whatsappTools.sendMessage(
    contacts.data[0].jid,
    "Automated message from Claude Code!"
  );
}

Data Model

Message Object

interface Message {
  id: string;                    // Unique message ID
  chat_jid: string;              // Chat identifier (JID)
  chat_name: string;             // Display name of chat
  sender: string;                // Phone number of sender
  content: string;               // Message text content
  timestamp: string;             // ISO 8601 timestamp
  is_from_me: boolean;           // true if sent by you
  media_type?: string;           // "image", "video", "audio", "document", or ""
}

Example:

{
  "id": "3EB0F2A8B9C4D1E5F6A7",
  "chat_jid": "1234567890@s.whatsapp.net",
  "chat_name": "John Doe",
  "sender": "1234567890",
  "content": "See you at the meeting!",
  "timestamp": "2025-10-26T14:30:00Z",
  "is_from_me": false,
  "media_type": ""
}

Contact Object

interface Contact {
  phone_number: string;          // Phone number (without country code prefix)
  name: string;                  // Display name
  jid: string;                   // WhatsApp JID
}

Example:

{
  "phone_number": "1234567890",
  "name": "John Doe",
  "jid": "1234567890@s.whatsapp.net"
}

Chat Object

interface Chat {
  jid: string;                   // Chat identifier
  name: string;                  // Display name
  last_message_time: string;     // ISO 8601 timestamp of last message
}

Example:

{
  "jid": "123456789@g.us",
  "name": "Project Team",
  "last_message_time": "2025-10-26T16:45:00Z"
}

JID (Jabber ID) Format

WhatsApp uses JIDs to identify chats:

TypeFormatExample
Individual{phone}@s.whatsapp.net1234567890@s.whatsapp.net
Group{group_id}@g.us120363012345678901@g.us

Extracting Components:

# Get phone number from individual JID
echo "1234567890@s.whatsapp.net" | cut -d'@' -f1
# Output: 1234567890

# Check if JID is a group
echo "$JID" | grep -q "@g.us" && echo "Group" || echo "Individual"

Storage & Persistence

Database Location

Default storage directory: ./store/

store/
├── whatsapp.db      # Session data (managed by whatsmeow)
└── messages.db      # Message history (managed by CLI)

Custom Location:

whatsapp-cli --store /var/lib/whatsapp chats list

Session Database (whatsapp.db)

  • Format: SQLite3
  • Managed by: whatsmeow library
  • Contents:
    • Device ID and encryption keys
    • Contact information
    • Session tokens
  • Persistence: ~20 days before re-authentication required
  • Security: Contains sensitive data. Protect it with file permissions.

Recommended Permissions:

chmod 600 store/whatsapp.db
chown $USER:$USER store/whatsapp.db

Message Database (messages.db)

  • Format: SQLite3
  • Managed by: WhatsApp CLI
  • Schema:
-- Chats table
CREATE TABLE chats (
    jid TEXT PRIMARY KEY,
    name TEXT,
    last_message_time TIMESTAMP
);

-- Messages table
CREATE TABLE messages (
    id TEXT,
    chat_jid TEXT,
    sender TEXT,
    content TEXT,
    timestamp TIMESTAMP,
    is_from_me BOOLEAN,
    media_type TEXT,
    filename TEXT,
    url TEXT,
    media_key BLOB,
    file_sha256 BLOB,
    file_enc_sha256 BLOB,
    file_length INTEGER,
    PRIMARY KEY (id, chat_jid),
    FOREIGN KEY (chat_jid) REFERENCES chats(jid)
);

Direct Database Access

# Open with sqlite3
sqlite3 store/messages.db

# Query examples
sqlite> SELECT COUNT(*) FROM messages;
sqlite> SELECT chat_name, COUNT(*) FROM messages
        JOIN chats ON messages.chat_jid = chats.jid
        GROUP BY chat_name;
sqlite> .quit

Backup & Migration

# Backup
tar -czf whatsapp-backup-$(date +%Y%m%d).tar.gz store/

# Restore
tar -xzf whatsapp-backup-20251026.tar.gz

# Migrate to new machine
rsync -avz store/ newserver:/path/to/store/

Authentication & Security

Authentication Flow

  1. First Run: QR code displayed
  2. User Action: Scan with WhatsApp mobile app
  3. Session Creation: Credentials saved to store/whatsapp.db
  4. Subsequent Runs: Auto-reconnect using saved session
  5. Expiration: After ~20 days, re-authentication required

Security Considerations

Session Protection

The store/whatsapp.db file contains your WhatsApp session credentials. Treat it like a password:

# Set proper permissions
chmod 600 store/whatsapp.db
chmod 700 store/

# Never commit to version control
echo "store/" >> .gitignore

# Use environment variable for custom location
export WHATSAPP_STORE="/secure/path/to/store"
whatsapp-cli --store "$WHATSAPP_STORE" chats list

Multi-Device Limit

WhatsApp allows up to 5 linked devices. If you reach this limit:

  1. Open WhatsApp on phone
  2. Go to Settings → Linked Devices
  3. Remove an old device
  4. Re-authenticate CLI

Network Security

  • All communication with WhatsApp uses end-to-end encryption
  • CLI communicates via WhatsApp Web protocol (WSS)
  • No data sent to third parties
  • Local storage only

Privacy

  • Message Storage: All messages stored locally in SQLite
  • No Cloud Sync: Data never leaves your machine
  • Media: Metadata stored, actual files downloaded on-demand
  • Deletion: Delete store/ directory to remove all data

Architecture

System Design

┌─────────────────────────────────────────────────┐
│                  whatsapp-cli                    │
│                   (Go Binary)                    │
├─────────────────────────────────────────────────┤
│  ┌───────────┐  ┌──────────┐  ┌─────────────┐  │
│  │  Commands │  │  Client  │  │   Storage   │  │
│  │   Layer   │→ │  Wrapper │→ │   (SQLite)  │  │
│  └───────────┘  └──────────┘  └─────────────┘  │
│        ↓             ↓                           │
│  ┌───────────┐  ┌──────────┐                    │
│  │   Output  │  │whatsmeow │                    │
│  │   (JSON)  │  │ Library  │                    │
│  └───────────┘  └──────────┘                    │
│                      ↓                           │
└──────────────────────┼──────────────────────────┘

              ┌────────────────┐
              │  WhatsApp Web  │
              │   API (WSS)    │
              └────────────────┘

Component Layers

LayerLocationResponsibility
CLImain.goArgument parsing, command routing
Commandsinternal/commands/Business logic for each command
Clientinternal/client/WhatsApp protocol wrapper
Storageinternal/store/Database operations
Outputinternal/output/JSON formatting

Dependencies

whatsmeow (github.com/tulir/whatsmeow)
├── WebSocket communication
├── Protocol buffer encoding/decoding
├── End-to-end encryption
└── Session management

go-sqlite3 (github.com/mattn/go-sqlite3)
└── SQLite database driver

qrterminal (github.com/mdp/qrterminal)
└── QR code rendering in terminal

Build Process

# Build with all dependencies statically linked
go build -ldflags="-s -w" -o whatsapp-cli .

# Cross-compile
GOOS=linux GOARCH=amd64 go build -o whatsapp-cli-linux .
GOOS=darwin GOARCH=arm64 go build -o whatsapp-cli-mac .
GOOS=windows GOARCH=amd64 go build -o whatsapp-cli.exe .

Binary Size Optimization:

# Standard build
go build -o whatsapp-cli .          # ~21MB

# Optimized build
go build -ldflags="-s -w" .         # ~15MB

# Ultra-compressed (requires UPX)
upx --best --lzma whatsapp-cli      # ~5MB

For more about the architecture, see docs/architecture.md.


Error Handling

Common Error Responses

Not Authenticated

{
  "success": false,
  "data": null,
  "error": "not authenticated"
}

Solution: Run whatsapp-cli auth

Connection Failed

{
  "success": false,
  "data": null,
  "error": "failed to connect: connection refused"
}

Solutions:

  • Check internet connection
  • Verify WhatsApp is active on phone
  • Check firewall rules

Invalid JID

{
  "success": false,
  "data": null,
  "error": "invalid JID format"
}

Solution: Use the correct JID format (phone@s.whatsapp.net or id@g.us)

Database Locked

{
  "success": false,
  "data": null,
  "error": "database is locked"
}

Solution: Close other instances of whatsapp-cli

Exit Codes

CodeMeaning
0Success
1General error (check JSON error field)

Troubleshooting

Authentication Issues

Problem: QR code does not appear

# Check terminal supports UTF-8
echo $LANG
# Should output: en_US.UTF-8 or similar

# Try with different QR code size
QRSIZE=small whatsapp-cli auth

Problem: "Session expired" after a few days

  • WhatsApp sessions expire after ~20 days
  • Re-run whatsapp-cli auth
  • Sessions also expire if you unlink the device from the phone

Connection Issues

Problem: "Connection timeout"

# Test network connectivity
ping -c 3 web.whatsapp.com

# Check DNS resolution
nslookup web.whatsapp.com

# Try with custom store path (permission issues)
whatsapp-cli --store /tmp/wa-test auth

Problem: "WebSocket connection failed"

  • Corporate firewalls can block WSS
  • A VPN can interfere with the connection
  • Try a different network

WhatsApp Web Version Bumps (405 / close 1006)

WhatsApp sometimes requires a newer desktop or web build. When that happens, the bundled whatsmeow dependency logs Client outdated (405). It then closes the websocket with code 1006. As a result, whatsapp-cli stops right after you scan the QR code.

To fix it:

  1. Bump the dependency:
    go get go.mau.fi/whatsmeow@latest
    go mod tidy
    
  2. Rebuild the binary:
    go build -o whatsapp-cli .
    
  3. Verify: Run ./whatsapp-cli version, then ./whatsapp-cli auth or ./whatsapp-cli sync. You will see ✓ Connected to WhatsApp with no Client outdated (405) messages.

If the upstream library does not yet have a new version, watch the tulir/whatsmeow issues for Client outdated (405) reports. Once a fix lands, repeat the steps above to update your local binary.

Database Issues

Problem: "Database is locked"

# Check for other processes
ps aux | grep whatsapp-cli

# Check for stale lock files
rm -f store/*.db-shm store/*.db-wal

# If corrupted, restore from backup
mv store/messages.db store/messages.db.bak
# Re-run cli to recreate

Problem: "Disk full" or "No space left"

# Check database size
du -h store/messages.db

# Vacuum database to reclaim space
sqlite3 store/messages.db "VACUUM;"

# Delete old messages
sqlite3 store/messages.db "DELETE FROM messages WHERE timestamp < datetime('now', '-30 days');"

Message Issues

Problem: Messages not appearing in searches

  • The CLI searches only messages stored locally
  • Messages received before the CLI was running are not stored
  • To re-sync, list messages from specific chats

Problem: Cannot send to a group

  • Use the group JID (it ends in @g.us), not a phone number
  • Get the group JID from the chats list command

Performance Issues

Problem: Slow searches on large databases

# Add indexes
sqlite3 store/messages.db <<EOF
CREATE INDEX IF NOT EXISTS idx_content ON messages(content);
CREATE INDEX IF NOT EXISTS idx_timestamp ON messages(timestamp);
CREATE INDEX IF NOT EXISTS idx_chat_jid ON messages(chat_jid);
EOF

Debug Mode

# Enable verbose logging (if implemented)
export WHATSAPP_DEBUG=1
whatsapp-cli chats list

# Check SQLite directly
sqlite3 store/messages.db "SELECT * FROM messages LIMIT 5;"

Development

Setting Up Development Environment

# Clone repository
git clone https://github.com/vicentereig/whatsapp-cli.git
cd whatsapp-cli

# Install dependencies
go mod download

# Run tests
go test ./...

# Run with race detector
go test -race ./...

# Build development version
go build -o whatsapp-cli-dev .

Building with a Local Module Cache

Some environments, for example sandboxes or CI systems, cannot write to the global Go module cache. If your environment has this limit, point GOMODCACHE to a writable directory in the repository before you build:

GOMODCACHE=$(pwd)/.gomodcache go build ./...

This command compiles the full project and stores downloaded modules under .gomodcache. The workspace then stays self-contained.

Running Tests

# All tests
go test ./...

# Specific package
go test ./internal/store

# With coverage
go test -cover ./...
go test -coverprofile=coverage.out ./...
go tool cover -html=coverage.out

# Verbose output
go test -v ./...

# Run specific test
go test -run TestStoreMessage ./internal/store

Code Structure

internal/
├── client/
│   └── client.go          # WhatsApp client wrapper
├── commands/
│   └── commands.go        # CLI command implementations
├── output/
│   ├── output.go          # JSON formatting
│   └── output_test.go     # Output tests
└── store/
    ├── store.go           # Database operations
    └── store_test.go      # Storage tests

Adding New Commands

  1. Define command in internal/commands/commands.go:
func (a *App) NewCommand(param string) string {
    // Implementation
    result := doSomething(param)
    return output.Success(result)
}
  1. Add routing in main.go:
case "newcommand":
    cmdFlags := flag.NewFlagSet("newcommand", flag.ExitOnError)
    param := cmdFlags.String("param", "", "description")
    cmdFlags.Parse(args[1:])
    result = app.NewCommand(*param)
  1. Write tests in internal/commands/commands_test.go:
func TestNewCommand(t *testing.T) {
    // Test implementation
}

Testing Guide

Follow TDD (Test-Driven Development):

  1. Write test first
  2. Run test (it fails)
  3. Implement feature
  4. Run test (it passes)
  5. Refactor

Example:

// store_test.go
func TestStoreMessage(t *testing.T) {
    store := setupTestDB(t)
    err := store.StoreMessage("id1", "chat@s.whatsapp.net", "sender",
        "Hello", time.Now(), false, "", "", "", nil, nil, nil, 0)
    assert.NoError(t, err)
}

Contributing

See CONTRIBUTING.md for guidelines.


FAQ

General

Q: Is this an official WhatsApp tool? A: No. This is a third-party tool. It uses the unofficial WhatsApp Web protocol, through the whatsmeow library.

Q: Can I use this with WhatsApp Business? A: Yes. It works with personal and business accounts.

Q: Does this work on multiple devices simultaneously? A: Yes. WhatsApp supports up to 5 linked devices.

Features

Q: Can I send images/videos? A: Not yet. The CLI currently sends text only. Media support is planned for a future release.

Q: Can I create/manage groups? A: Not yet. The CLI currently gives read-only access to groups.

Q: Can I see delivery/read receipts? A: Not in the current version. This feature is planned for a future release.

Q: Can I receive real-time messages? A: Yes. Run whatsapp-cli sync to receive and store messages continuously. Run it in the background or in a tmux session.

Technical

Q: Why Go instead of Python/Node.js? A: Go gives single-binary distribution and strong performance. Also, whatsmeow is written in Go.

Q: Can I run this in Docker? A: Yes, but QR code authentication needs terminal access. Use the -it flags for interactive mode.

Q: Does it support proxy/VPN? A: The CLI respects system proxy settings. Set the HTTP_PROXY and HTTPS_PROXY environment variables.

Q: Can I access messages from before installing CLI? A: Yes. When you run whatsapp-cli sync, it downloads message history from WhatsApp servers. The amount of history depends on how long WhatsApp retains it on its servers.

Privacy & Security

Q: Is my data safe? A: The CLI stores all data locally, with no cloud sync. Protect your store/ directory.

Q: Can WhatsApp detect/ban this? A: The CLI uses the official WhatsApp Web protocol. The risk is low, but use it at your own discretion.

Q: Is end-to-end encryption maintained? A: Yes. WhatsApp's protocol encrypts all messages end-to-end.


Credits

Authors

  • Vicente Reig - Initial development
  • Built with assistance from Claude Code (Anthropic)

Dependencies

License

MIT License - see LICENSE file

Support

Acknowledgments

  • WhatsApp for the multidevice protocol
  • Tulir Asokan for the excellent whatsmeow library
  • Go community for excellent tooling
  • Anthropic for Claude Code

Last Updated: 2025-12-13 Version: 1.3.2 Documentation: https://github.com/vicentereig/whatsapp-cli/blob/main/README.md