Skip to content

Instantly share code, notes, and snippets.

@mdp
Last active May 21, 2026 01:17
Show Gist options
  • Select an option

  • Save mdp/6fae4a9b0ed4452f8a4f46a464a5fcdf to your computer and use it in GitHub Desktop.

Select an option

Save mdp/6fae4a9b0ed4452f8a4f46a464a5fcdf to your computer and use it in GitHub Desktop.
WhatsApp Gateway — Agent Integration Guide

WhatsApp Gateway — Agent Integration Guide

This service bridges WhatsApp to a simple HTTP + WebSocket API. This guide tells you everything you need to operate it as an agent.

Base URL

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".


Receiving Messages — WebSocket

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.

Event: connection status

Sent immediately on connect, and again whenever the WhatsApp connection state changes.

{ "event": "connection", "status": "open", "ts": 1715000000000 }

Event: incoming message

{
  "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.

Mention detection in groups

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("/") || botWasMentioned

mentionedJid is populated from Baileys contextInfo.mentionedJid and is authoritative.

Keep msgKey — you need it to send a realistic reply.


Sending Messages

Option A — /reply (recommended)

Use this when responding to an inbound message. It performs the full human-like sequence automatically:

  1. Marks the original message as read (double blue tick on their end)
  2. Waits 2–5 seconds (simulates reading)
  3. Sends a "typing..." presence indicator
  4. Waits ~50ms per character (simulates typing, clamped to 2–8 seconds)
  5. 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.

Option B — /send (no read receipt or typing indicator)

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 }

JID Format

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.


Rate Limiting

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... }

Error Handling

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

Reconnection Behavior

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.


Minimal Agent Loop (pseudocode)

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 }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment