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
- Installation
- Quick Start
- Complete Command Reference
- JSON Response Format
- Usage Examples
- Integration with LLMs/AI Tools
- Data Model
- Storage & Persistence
- Authentication & Security
- Architecture
- Error Handling
- Troubleshooting
- Development
- FAQ
- Credits
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
| Feature | Description |
|---|---|
| Zero Dependencies | Single compiled binary (21MB), no runtime required |
| JSON Output | All commands return structured JSON for easy parsing |
| Persistent Sessions | Authenticate once via QR code, auto-reconnect for ~20 days |
| Local Storage | SQLite database, no cloud dependencies |
| Full Messaging | Send, receive, search messages; manage contacts & chats |
| Group Support | Send/receive messages in group chats |
| TDD Implementation | 100% 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
Method 1: Homebrew (Recommended)
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 examplev1.0.0, withgo 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, runshasum -a 256 -c checksums.txt --ignore-missingto verify the archive. - Binaries use the name
whatsapp-cli-<os>-<arch>(Windows adds.exe). After you extract the file, mark it executable withchmod +xand place it in yourPATH, 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, runcosign 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 coversgo install, source builds, and GitHub Release automation.
Quick Start
Step 1: First-Time Authentication
whatsapp-cli auth
What happens:
- QR code appears in terminal
- Open WhatsApp on your phone → Settings → Linked Devices → Link a Device
- Scan the QR code
- 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:
- Connects to WhatsApp and stays connected
- Downloads message history from WhatsApp servers
- Receives new messages in real-time
- Stores everything in
./store/messages.db - 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:
| Flag | Type | Default | Description |
|---|---|---|---|
--store | string | ./store | Directory 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.dbwith 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.Messagefor real-time messages - Processes
*events.HistorySyncfor 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:
- Initial Setup: Run once to download all message history
- Continuous Sync: Run as background service to receive messages
- Periodic Sync: Run via cron to update messages on a schedule
- 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:
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--chat | string | No | - | Filter by chat JID (e.g., 1234567890@s.whatsapp.net) |
--limit | int | No | 20 | Maximum number of messages to return |
--page | int | No | 0 | Page 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)
Command: messages search
Search messages by content across all chats.
Syntax:
whatsapp-cli messages search --query TEXT [OPTIONS]
Parameters:
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--query | string | Yes | - | Search term (case-insensitive, partial match) |
--limit | int | No | 20 | Maximum number of results |
--page | int | No | 0 | Page 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
Command: contacts search
Search contacts by name or phone number.
Syntax:
whatsapp-cli contacts search --query TEXT
Parameters:
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--query | string | Yes | - | 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:
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--query | string | No | - | Filter chats by name or JID |
--limit | int | No | 20 | Maximum number of chats |
--page | int | No | 0 | Page 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:
| Flag | Type | Required | Default | Description |
|---|---|---|---|---|
--to | string | Yes | - | Phone number or JID |
--message | string | Yes | - | Message text content |
Recipient Formats:
| Format | Example | Use Case |
|---|---|---|
| Phone number | 1234567890 | Individual chats (auto-converted to JID) |
| Individual JID | 1234567890@s.whatsapp.net | Individual chats |
| Group JID | 123456789@g.us | Group 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
sendcommand supports text messages only. To download attachments, usemedia 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:
| Flag | Type | Required | Description |
|---|---|---|---|
--message-id | string | Yes | Message identifier from messages list/search |
--chat | string | No | Chat JID to disambiguate duplicate message IDs |
--output | string | No | Destination 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
--outputpoints 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 synccaptured 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
| Command | Data Type | Structure |
|---|---|---|
auth | object | {"authenticated": bool, "message": string} |
messages list | array | [Message, ...] |
messages search | array | [Message, ...] |
contacts search | array | [Contact, ...] |
chats list | array | [Chat, ...] |
send | object | {"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:
| Type | Format | Example |
|---|---|---|
| Individual | {phone}@s.whatsapp.net | 1234567890@s.whatsapp.net |
| Group | {group_id}@g.us | 120363012345678901@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
- First Run: QR code displayed
- User Action: Scan with WhatsApp mobile app
- Session Creation: Credentials saved to
store/whatsapp.db - Subsequent Runs: Auto-reconnect using saved session
- 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:
- Open WhatsApp on phone
- Go to Settings → Linked Devices
- Remove an old device
- 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
| Layer | Location | Responsibility |
|---|---|---|
| CLI | main.go | Argument parsing, command routing |
| Commands | internal/commands/ | Business logic for each command |
| Client | internal/client/ | WhatsApp protocol wrapper |
| Storage | internal/store/ | Database operations |
| Output | internal/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
| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | General 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:
- Bump the dependency:
go get go.mau.fi/whatsmeow@latest go mod tidy - Rebuild the binary:
go build -o whatsapp-cli . - Verify: Run
./whatsapp-cli version, then./whatsapp-cli author./whatsapp-cli sync. You will see✓ Connected to WhatsAppwith noClient 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 listcommand
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
- Define command in
internal/commands/commands.go:
func (a *App) NewCommand(param string) string {
// Implementation
result := doSomething(param)
return output.Success(result)
}
- 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)
- Write tests in
internal/commands/commands_test.go:
func TestNewCommand(t *testing.T) {
// Test implementation
}
Testing Guide
Follow TDD (Test-Driven Development):
- Write test first
- Run test (it fails)
- Implement feature
- Run test (it passes)
- 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
- whatsmeow by Tulir Asokan - WhatsApp Web client library
- go-sqlite3 - SQLite driver
- qrterminal - QR code generation
License
MIT License - see LICENSE file
Support
- Issues: https://github.com/vicentereig/whatsapp-cli/issues
- Discussions: https://github.com/vicentereig/whatsapp-cli/discussions
- Pull Requests: Welcome! See CONTRIBUTING.md
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