| name | whatsapp |
| description | WhatsApp automation (Green API primary, WAHA fallback). Use for: send/receive messages, send voice/images/files, retrieve incoming messages (text, voice, images), transcribe voice messages. WAME = 'WhatsApp ME' = send me by WhatsApp. |
| enhancedBy | [{"get-contact":"Auto-lookup phone by name. Without it: ask user for phone directly"}] |
WhatsApp Automation
Send messages via unified script with Green API (primary) + WAHA (fallback if configured).
Quick Start - Unified Script
cd ~/.claude/skills/whatsapp/scripts
npx ts-node send-message.ts "Hello!" --to 972501234567
npx ts-node send-message.ts $'Line 1\n\nLine 2\n- Bullet A\n- Bullet B' --to 972501234567
npx ts-node send-message.ts --to 972501234567 --image /path/to/image.jpg --caption "Check this out!"
npx ts-node send-message.ts --to 972501234567 --video /path/to/video.mp4 --caption "Watch this!"
npx ts-node send-message.ts --to 972501234567 --media /path/to/any-file --caption "Here!"
npx ts-node send-message.ts --to 972501234567 --voice /path/to/audio.ogg
npx ts-node send-message.ts --to 972501234567 --file /path/to/doc.pdf --caption "Here's the doc"
npx ts-node send-message.ts "Group message" --group 120363xxx@g.us
npx ts-node send-message.ts "Reply!" --group 120363xxx@g.us --quote BAE5367237E13A1B
npx ts-node send-message.ts --group 120363xxx@g.us --voice /tmp/voice.mp3 --quote BAE5367237E13A1B
npx ts-node send-message.ts "Test" --to 972501234567 --dry-run
Default Numbers
| Alias | Number | Use Case |
|---|
| myself / me / test | $MY_PHONE (from config.sh) | Default recipient |
Sending Video & Audio โ encode to AAC
WhatsApp (and Facebook) play a video silently if its audio track is MP3 muxed into an MP4 โ the file is valid and has sound in desktop players, but the mobile app drops it. Always ship video with AAC audio:
ffmpeg -y -i in.mp4 -c:v copy -c:a aac -b:a 192k -movflags +faststart out.mp4
ffmpeg -i out.mp4 -af volumedetect -f null /dev/null 2>&1 | grep mean_volume
Voice notes: convert the source to OGG/Opus first โ raw MP3 in a container is unreliable in the app.
Script Options
| Option | Description |
|---|
"message" | Text message (first positional argument) |
--to, --phone <NUMBER> | Phone number (Israeli format accepted) |
--group <ID> | Group ID (format: 120363xxx@g.us) |
--image <PATH> | Attach image file |
--video <PATH> | Attach video file (MP4, etc.) |
--media <PATH> | Attach any media file (auto-detect) |
--voice <PATH> | Send voice message (OGG/MP3) |
--file <PATH> | Attach document/file |
--caption <TEXT> | Caption for media files |
--quote <MSG_ID> | Reply to a specific message (quotedMessageId) |
--dry-run | Preview without sending |
Multiline Formatting
When sending a WhatsApp message with line breaks from the shell, do not pass literal \n inside a normal quoted string if you want actual newlines in the final message.
Use shell ANSI-C quoting instead:
npx ts-node send-message.ts $'Hi\n\nLine 2\n- Item 1\n- Item 2' --group 120363xxx@g.us
Why:
"text with \n" sends the characters \ + n โ but the script auto-converts these to real newlines
$'text with \n' sends real line breaks natively
Either form works โ the script normalizes literal \n to real newlines before sending.
Apostrophes inside $'...': Use \' directly โ do NOT use the '\'' trick. The '\'' pattern breaks out of ANSI-C quoting, and everything after it becomes regular single-quoted (where \n is literal).
$'I\'ve added a check.\n\nNext line.'
# WRONG โ breaks ANSI-C quoting, \n becomes literal after the apostrophe
$'I'\''ve added a check.\n\nNext line.'
For structured WhatsApp messages, prefer:
- short opening line
- blank line
- short bullet list
- blank line
- closing line
Formatting Rule โ Match Language
English messages: Use regular bullets/dashes. Never use Hebrew letters (ื. ื. ื.) in English text.
- First item
- Second item
- Third item
Hebrew messages: Every line must start with a Hebrew character for correct RTL rendering. Use Hebrew letters for list items.
ื. ืคืจืื ืจืืฉืื
ื. ืคืจืื ืฉื ื
ื. ืคืจืื ืฉืืืฉื
Don't mix: No Hebrew letters in English messages, no dashes/numbers in Hebrew messages.
Don't:
- ืคืจืื ืจืืฉืื
1. ืคืจืื ืฉื ื
* ืคืจืื ืฉืืืฉื
Lines starting with non-Hebrew characters (dashes, numbers, asterisks, English) break RTL alignment in WhatsApp. Always ensure the first visible character is Hebrew.
Provider Logic
| Content Type | Primary | Fallback |
|---|
| Text | Green API | WAHA (if configured) |
| Image | Green API | - |
| Video | Green API | - |
| Voice | Green API | - |
| File | Green API | - |
Note: WAHA is not currently installed. Green API handles all message types.
Configuration
Credentials in scripts/.env:
# Green API (primary provider)
GREEN_API_URL=https://7103.api.greenapi.com
GREEN_API_INSTANCE=your_instance
GREEN_API_TOKEN=your_token
# WAHA (optional fallback โ uncomment if installed)
# WAHA_URL=http://localhost:3001
# WAHA_API_KEY=your_key
# WAHA_SESSION=default
Phone Number Format
| Input | Normalized |
|---|
972501234567 | 972501234567@c.us |
0501234567 | 972501234567@c.us |
501234567 | 972501234567@c.us |
Direct API (WAHA)
For text messages only:
source ~/.claude/skills/whatsapp/config.sh
curl -s -X POST "$WAHA_URL/api/sendText" \
-H "X-Api-Key: $WAHA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"session": "default",
"chatId": "972501234567@c.us",
"text": "Hello!"
}'
Check Session Status
source ~/.claude/skills/whatsapp/config.sh
curl -s -H "X-Api-Key: $WAHA_API_KEY" "$WAHA_URL/api/sessions/default"
Finding Contact by Name
When user says "send WhatsApp to [name]" without phone number:
- Use
get-contact skill to search CRM
- If not found โ ask user for phone
- If multiple โ ask user to choose
- If one โ confirm before sending
Always confirm contact before sending.
Finding a Group by Name or Project
When user says "send to [project] group" โ search Green API directly (WAHA may be down):
TOKEN=$(grep "GREEN_API_TOKEN" ~/.claude/skills/whatsapp/scripts/.env | cut -d'=' -f2)
INSTANCE=$(grep "GREEN_API_INSTANCE" ~/.claude/skills/whatsapp/scripts/.env | cut -d'=' -f2)
curl -s "https://7103.api.greenapi.com/waInstance$INSTANCE/getChats/$TOKEN" | python3 -c "
import json,sys
data=json.load(sys.stdin)
for c in (data if isinstance(data,list) else []):
if '@g.us' in c.get('id',''):
print(c.get('id'), '|', c.get('name',''))
"
Then send using send-message.ts --group <ID>.
Never check WAHA session before sending โ the script handles fallback automatically.
Provider Notes
Green API is the primary provider. WAHA is not installed and only used as fallback if explicitly configured.
Just run send-message.ts โ it uses Green API directly.
Retrieving Messages (get-messages.ts)
Fetch incoming messages, voice recordings, and media from any contact:
npx ts-node get-messages.ts --chat 972526062921 --count 5
npx ts-node get-messages.ts --chat 972526062921 --minutes 120
npx ts-node get-messages.ts --start "2h ago" --end "now"
npx ts-node get-messages.ts --chat 972526062921 --json
What you get:
- Text messages with full content
- Voice messages with download URL (OGA/OGG format) โ can be transcribed separately
- Images with download URL and metadata
- Timestamps, sender info, file names
- All message types (extended text, reactions, media)
Quoted/reply messages are shown inline โ get-messages.ts now displays quoted context automatically with โณ Reply to prefix. Both extendedTextMessage and quotedMessage types are handled.
Message types: textMessage, extendedTextMessage (with .text), quotedMessage (reply with .quotedMessage.textMessage), reactionMessage, imageMessage
Voice Message Workflow:
- Get messages with
get-messages.ts --json
- Extract
downloadUrl from audioMessage type
- Download the OGA file
- Convert to WAV:
ffmpeg -i file.oga -ar 16000 file.wav
- Transcribe:
/transcribe --language he /path/to/file.wav
Waiting for Replies โ Monitor Integration
Sometimes after sending a message you need the contact's reply before you can continue โ an approval, a decision, feedback, a "how much do I owe you". Instead of stopping and asking the user to watch the chat, hand the waiting to the Monitor tool and keep working; each new reply arrives as a notification.
Use scripts/watch-replies.sh โ it polls for genuinely new incoming messages (dedupes by id, handles text / reactions / media) and prints one line per reply, which is exactly what Monitor consumes. Exits only when killed (Monitor timeout / TaskStop).
scripts/watch-replies.sh --chat <number|groupId> [--since <epoch>] [--interval 30] [--label REPLY]
Launch it through the Monitor tool (not Bash), so replies notify you while you keep working:
command: ~/.claude/skills/whatsapp/scripts/watch-replies.sh --chat 972501234567 --since "$(date +%s)" --label CLIENT
timeout_ms: how long to wait (e.g. 3600000 for an hour).
- Always set
--since to the moment you sent ($(date +%s)), or old history re-fires as if new.
โ ๏ธ Do NOT hand-roll an inline poller in the Monitor command. Passing a group id or JSON body directly in the shell breaks under zsh โ @g.us triggers bad math expression: illegal character: @, and the monitor exits 1. watch-replies.sh takes the chat as an argument (--chat <id>), so group ids are safe. Always reach for this script; never inline the group id + curl/python in the Monitor command.
Decide per situation whether to use it
Reach for it when the reply changes what you do next:
- you sent something that needs approval before the next step (a draft, a preview, a quote);
- you asked a question and will act on the answer;
- you're in an iterative loop (send fix โ they review โ send next);
- you started long work and want to hear back without blocking.
Skip it for fire-and-forget (a confirmation, an FYI, a "done") โ no reply is expected, so there's nothing to watch.
Staying in the conversation
Often the right move is to remain engaged: when a reply lands, respond, then re-arm a fresh watcher (new --since) for the next turn. That's a real back-and-forth โ send sample โ they approve โ send fix โ wait again โ until the thread naturally closes (final approval, thanks, payment settled, or the user says wrap up). Each round: send โ watch โ reply โ re-watch. Use your judgement on when the exchange is genuinely done and it's time to stop watching.
Discipline
- Each watcher has its own dedupe file, so two watchers on the same chat double-fire โ
TaskStop the old one before arming a new one.
- Reactions surface as
(reaction ๐) and media as [imageMessage]; when you need the image URL / caption / voice file, pull the full record with get-messages.ts --json.
When to Use
- Send notifications or alerts
- Share images/posters
- Voice messages
- Document sharing
- Automated reminders
- Workshop follow-ups
- Retrieve and transcribe voice messages from customers
- Check media attachments from contacts