# Claude on WhatsApp, in one file

A WhatsApp bot that answers with Claude. Inbound messages arrive at your
webhook from WABridges, you call the Anthropic Messages API, you POST the
answer back. Same program in Node (`node/bot.js`) and Python (`python/bot.py`),
about 80 lines each.

What it does:

- Verifies the `X-Webhook-Signature` on every event (HMAC, constant-time).
- Acks the webhook immediately and answers in the background, so the bridge
  never retries a slow reply.
- Ignores its own messages, groups, and non-text events; de-duplicates on
  `X-Webhook-Event-Id` (delivery is at-least-once).
- Keeps the last 10 exchanges per chat in memory as conversation history.
- Calls `claude-sonnet-5` at low effort (chat is latency-sensitive), handles
  a `refusal` stop reason and rate limits with a short reply.
- Sends the reply with an `Idempotency-Key`, and retries once after a
  `503 bridge_starting` (an idle bridge waking up), honoring `Retry-After`.

## Prerequisites

- A WABridges API key: Dashboard -> API keys -> create (starts with `sk_`,
  shown once).
- An Anthropic API key (`ANTHROPIC_API_KEY`).
- Node 18+ or Python 3.10+.
- A public URL for your local server: `ngrok http 3000` or
  `cloudflared tunnel --url http://localhost:3000` both work.

## Step 1: run it against the sandbox (no phone)

Every account has a simulated bridge at `customer_ref = sandbox` with one
contact, Alice, who replies to everything a few seconds later.

```sh
# Node
cd node && npm install
# Python
cd python && pip install -r requirements.txt
```

Point the sandbox at your tunnel. The response includes the sandbox
`webhook_secret`:

```sh
curl -X PATCH https://wabridges.com/api/instances/sandbox \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"webhook_url": "https://<your-tunnel>/hook"}'
```

Run the bot:

```sh
export WA_API_KEY=sk_...            # your API key
export WA_WEBHOOK_SECRET=whsec_sbx_...   # from the PATCH response
export ANTHROPIC_API_KEY=sk-ant-...
node bot.js          # or: python bot.py
```

Kick off a conversation by sending Alice anything:

```sh
curl -X POST https://wabridges.com/api/instances/sandbox/proxy/send/text \
  -H "Authorization: Bearer $WA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"chat": "15550001234", "body": "hi"}'
```

Alice replies, your webhook fires, Claude answers, Alice answers back. After
about six exchanges Alice says "Looks like we're looping" and goes quiet for a
minute. That is the sandbox's loop guard (she answers every message, and so
does your bot; humans don't). Seeing it means the whole loop works.

## Step 2: a real number

1. Create a bridge. The first one is free for the trial period, no card:

   ```sh
   curl -X POST https://wabridges.com/api/instances \
     -H "Authorization: Bearer $WA_API_KEY" \
     -H "Content-Type: application/json" \
     -d '{"customer_ref": "claude-bot", "webhook_url": "https://<your-tunnel>/hook"}'
   ```

   Keep the `webhook_secret` from the response.
2. Pair a phone: open the bridge on the dashboard and scan the QR, or
   request a pairing code via `POST .../proxy/pair`. About a minute, phone in
   hand. Any number works: personal, VoIP, or a spare SIM.
3. Restart the bot with `CUSTOMER_REF=claude-bot` and that bridge's
   `WA_WEBHOOK_SECRET`. Message the number from another phone.

If the bridge sat idle for a few hours before anyone paired it, the first send
returns `503 bridge_starting` with `Retry-After`; the bot waits and retries
once.

## Before production

- History lives in memory and resets on restart. Move it to a store keyed by
  `chat_id`.
- Group chats are ignored. Handle `is_group` if you want the bot in groups.
- Add a per-chat rate limit so a chatty contact cannot run up your Claude bill.
- Raise `output_config.effort` (and `max_tokens`) for tasks that need more
  reasoning than small talk.
- Prefer the SDK for the Anthropic side; if you want zero dependencies, the
  call is one `POST https://api.anthropic.com/v1/messages` with `x-api-key`
  and `anthropic-version: 2023-06-01` headers.
