This service bridges WhatsApp to a simple HTTP + WebSocket API. This guide tells you everything you need to operate it as an agent.
https://whatsapp.anteater-snapper.ts.net (HTTPS, port 443)
wss://whatsapp.anteater-snapper.ts.net (WebSocket over TLS, port 443)
The service must be reachable on your Tailscale network. Before doing anything, confirm the WhatsApp connection is live:
GET /status
→ { "connection": "open" | "connecting" | "close" }
Do not attempt to send messages unless connection is "open".
Connect to wss://whatsapp.anteater-snapper.ts.net/ws and keep the connection open. All incoming WhatsApp messages are pushed to you in real time.
Sent immediately on connect, and again whenever the WhatsApp connection state changes.
{ "event": "connection", "status": "open", "ts": 1715000000000 }{
"event": "message",
"from": "15551234567@s.whatsapp.net",
"text": "Hey @Bot are you around?",
"mentionedJid": ["19998887777@s.whatsapp.net"],
"pushName": "Alice",
"rawMessageType": "extendedTextMessage",
"msgKey": {
"id": "ABCD1234EFGH5678",
"remoteJid": "15551234567@s.whatsapp.net",
"fromMe": false
},
"ts": 1715000000000
}Important: You will also receive events for messages sent by this account (e.g. messages you sent via /send or /reply). These arrive with fromMe: true in msgKey. You must ignore them to avoid reply loops. Only act on messages where msgKey.fromMe is false.
Do not use @Name text in text to detect whether the bot was addressed — WhatsApp contact display names are client-side and unreliable. Use mentionedJid instead:
const botWasMentioned = msg.mentionedJid?.includes(BOT_WHATSAPP_JID)
const addressed = msg.text.trim().startsWith("/") || botWasMentionedmentionedJid is populated from Baileys contextInfo.mentionedJid and is authoritative.
Keep msgKey — you need it to send a realistic reply.
Use this when responding to an inbound message. It performs the full human-like sequence automatically:
- Marks the original message as read (double blue tick on their end)
- Waits 2–5 seconds (simulates reading)
- Sends a "typing..." presence indicator
- Waits ~50ms per character (simulates typing, clamped to 2–8 seconds)
- Sends the message
POST /reply
Content-Type: application/json
{
"jid": "15551234567@s.whatsapp.net",
"text": "Yeah, what's up?",
"msgKey": {
"id": "ABCD1234EFGH5678",
"remoteJid": "15551234567@s.whatsapp.net",
"fromMe": false
}
}
Response:
{ "ok": true, "readDelay": 3241, "typingDelay": 2000, "antibanDelay": 850 }This endpoint is intentionally slow — it blocks for the full simulated delay (up to ~15 seconds total) before returning. Plan accordingly; do not set short timeouts.
Use for proactive messages not triggered by an inbound message (reminders, alerts, etc.).
POST /send
Content-Type: application/json
{
"jid": "15551234567@s.whatsapp.net",
"text": "Your order has shipped."
}
Response:
{ "ok": true, "delayMs": 850 }WhatsApp identifiers (JIDs) follow this format:
| Type | Format | Example |
|---|---|---|
| Individual | {country_code}{number}@s.whatsapp.net |
15551234567@s.whatsapp.net |
| Group | {group_id}@g.us |
120363000000000001@g.us |
Always use the from field from an inbound message event as the jid when replying — do not construct JIDs manually.
The service uses an antiban middleware on the conservative preset. If you exceed safe send rates, /send and /reply will return HTTP 429:
{ "ok": false, "reason": "daily limit reached" }Back off and do not retry the same message. Check /stats for current usage:
GET /stats
→ { ...antiban counters... }
| Status | Meaning |
|---|---|
200 ok: true |
Message queued and sent |
| 400 | Missing jid or text |
| 429 | Antiban rate limit hit — back off |
| 503 | WhatsApp not connected — check /status and wait |
| 500 | Send failed (WhatsApp error) — do not retry immediately |
The service automatically reconnects to WhatsApp after drops. While reconnecting, /status returns "connecting" and send endpoints return 503. Watch for { "event": "connection", "status": "open" } on the WebSocket before retrying.
If you lose the WebSocket connection, reconnect immediately — there is no message replay, so messages received while disconnected are lost.
connect websocket to wss://whatsapp.anteater-snapper.ts.net/ws
on message received:
if event == "connection":
update local connection_state
if event == "message" and msgKey.fromMe == false:
reply_text = generate_reply(text)
POST /reply { jid: from, text: reply_text, msgKey: msgKey }