Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions channels/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ Complete documentation for all messaging platform integrations in GoClaw.
5. **[Larksuite](./larksuite.md)** — WebSocket/Webhook, streaming cards, media
6. **[Zalo OA](./zalo-oa.md)** — Official Account, DM-only, pairing, images
7. **[Zalo Personal](./zalo-personal.md)** — Personal account (unofficial), DM + groups
8. **[WhatsApp](./whatsapp.md)** — External bridge, JSON protocol, auto-reconnect
8. **[WhatsApp](./whatsapp.md)** — Direct connection, QR auth, media, typing indicators, pairing
9. **[WebSocket](./websocket.md)** — Direct RPC, custom clients, streaming events
10. **[Browser Pairing](./browser-pairing.md)** — 8-char code auth, session tokens

## Channel Comparison Table

| Feature | Telegram | Discord | Slack | Larksuite | Zalo OA | Zalo Pers | WhatsApp | WebSocket |
|---------|----------|---------|-------|--------|---------|-----------|----------|-----------|
| **Setup Complexity** | Easy | Easy | Easy | Medium | Medium | Hard | Hard | Very Easy |
| **Transport** | Polling | Gateway | Socket Mode | WS/Webhook | Polling | Protocol | Bridge | WebSocket |
| **Setup Complexity** | Easy | Easy | Easy | Medium | Medium | Hard | Medium | Very Easy |
| **Transport** | Polling | Gateway | Socket Mode | WS/Webhook | Polling | Protocol | Direct connection | WebSocket |
| **DM Support** | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A |
| **Group Support** | Yes | Yes | Yes | Yes | No | Yes | Yes | N/A |
| **Streaming** | Yes | Yes | Yes | Yes | No | No | No | Yes |
| **Rich Format** | HTML | Markdown | mrkdwn | Cards | Plain | Plain | JSON | JSON |
| **Rich Format** | HTML | Markdown | mrkdwn | Cards | Plain | Plain | WA native | JSON |
| **Reactions** | Yes | -- | Yes | Yes | -- | -- | -- | -- |
| **Media** | Photos, Voice, Files | Files, Embeds | Files (20MB) | Images, Files | Images | -- | JSON | N/A |
| **Auth Method** | Token | Token | 3 Tokens | App ID + Secret | API Key | Credentials | Bridge | Token + Pairing |
| **Media** | Photos, Voice, Files | Files, Embeds | Files (20MB) | Images, Files | Images | -- | Images, Video, Audio, Docs | N/A |
| **Auth Method** | Token | Token | 3 Tokens | App ID + Secret | API Key | Credentials | QR Code | Token + Pairing |
| **Risk Level** | Low | Low | Low | Low | Low | High | Medium | Low |

## Configuration Files
Expand Down Expand Up @@ -142,9 +142,9 @@ Flexible format supporting:

### WhatsApp

- [ ] Deploy WhatsApp bridge service (e.g., whatsapp-web.js)
- [ ] Copy WebSocket URL
- [ ] Enable in config
- [ ] Create channel in UI: Channels > Add Channel > WhatsApp
- [ ] Scan QR code with WhatsApp (You > Linked Devices > Link a Device)
- [ ] Configure DM/group policies as needed

### WebSocket

Expand Down
190 changes: 106 additions & 84 deletions channels/whatsapp.md
Original file line number Diff line number Diff line change
@@ -1,141 +1,163 @@
# WhatsApp Channel

WhatsApp integration via external WebSocket bridge. GoClaw connects to a bridge service (e.g., whatsapp-web.js) that handles the WhatsApp protocol.
Direct WhatsApp integration. GoClaw connects directly to WhatsApp's multi-device protocol — no external bridge or Node.js service required. Auth state is stored in the database (PostgreSQL or SQLite).

## Setup

**Prerequisites:**
- Running WhatsApp bridge service (e.g., whatsapp-web.js)
- Bridge URL accessible to GoClaw
1. **Channels > Add Channel > WhatsApp**
2. Choose an agent, click **Create & Scan QR**
3. Scan the QR code with WhatsApp (You > Linked Devices > Link a Device)
4. Configure DM/group policies as needed

**Start WhatsApp Bridge:**
That's it — no bridge to deploy, no extra containers.

Example using whatsapp-web.js:
### Config File Setup

```bash
npm install -g whatsapp-web.js
# Start bridge server on localhost:3001
whatsapp-bridge --port 3001
```

Your bridge should expose a WebSocket endpoint (e.g., `ws://localhost:3001`).

**Enable WhatsApp:**
For config-file-based channels (instead of DB instances):

```json
{
"channels": {
"whatsapp": {
"enabled": true,
"bridge_url": "ws://localhost:3001",
"dm_policy": "open",
"group_policy": "open",
"allow_from": []
"dm_policy": "pairing",
"group_policy": "pairing"
}
}
}
```

## Configuration

All config keys are in `channels.whatsapp`:
All config keys are in `channels.whatsapp` (config file) or the instance config JSON (DB):

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | bool | false | Enable/disable channel |
| `bridge_url` | string | required | WebSocket URL to bridge (e.g., `ws://bridge:3001`) |
| `enabled` | bool | `false` | Enable/disable channel |
| `allow_from` | list | -- | User/group ID allowlist |
| `dm_policy` | string | `"open"` | `open`, `allowlist`, `pairing`, `disabled` |
| `group_policy` | string | `"open"` | `open`, `allowlist`, `disabled` |
| `dm_policy` | string | `"pairing"` | `pairing`, `open`, `allowlist`, `disabled` |
| `group_policy` | string | `"pairing"` (DB) / `"open"` (config) | `pairing`, `open`, `allowlist`, `disabled` |
| `require_mention` | bool | `false` | Only respond in groups when bot is @mentioned |
| `history_limit` | int | `200` | Max pending group messages for context (0=disabled) |
| `block_reply` | bool | -- | Override gateway block_reply (nil=inherit) |

## Features

### Bridge Connection

GoClaw connects to the bridge via WebSocket and sends/receives JSON messages.
## Architecture

```mermaid
flowchart LR
GC["GoClaw"]
WS["WebSocket<br/>Connection"]
BRIDGE["WhatsApp<br/>Bridge"]
WA["WhatsApp<br/>Servers"]
GC["GoClaw"]
UI["Web UI<br/>(QR Wizard)"]

GC -->|"JSON messages"| WS
WS -->|"JSON messages"| BRIDGE
BRIDGE -->|"WhatsApp protocol"| WA
WA -->|"Protocol"| BRIDGE
BRIDGE -->|"JSON events"| WS
WS -->|"Events"| GC
WA <-->|"Multi-device protocol"| GC
GC -->|"QR events via WS"| UI
```

### DM and Group Support
- **GoClaw** connects directly to WhatsApp servers via multi-device protocol
- Auth state is stored in the database — survives restarts
- One channel instance = one WhatsApp phone number
- No bridge, no Node.js, no shared volumes

Bridge detects group chats via `@g.us` suffix in chat ID:
## Features

- **DM**: `"1234567890@c.us"`
- **Group**: `"123-456@g.us"`
### QR Code Authentication

Policies apply accordingly (DM policy for DMs, group policy for groups).
WhatsApp requires QR code scanning to link a device. The flow:

In group chats, messages include a `[From:]` annotation with the sender's display name, allowing the agent to distinguish between participants.
1. GoClaw generates QR code for device linking
2. QR string is encoded as PNG (base64) and sent to the UI wizard via WS event
3. Web UI displays the QR image
4. User scans with WhatsApp (You > Linked Devices > Link a Device)
5. Connection confirmed via auth event

### Message Format
**Re-authentication**: Use the "Re-authenticate" button in the channels table to force a new QR scan (logs out the current WhatsApp session and deletes stored device credentials).

Messages are JSON objects:
### DM and Group Policies

```json
{
"from": "1234567890@c.us",
"body": "Hello!",
"type": "chat",
"id": "message_id_123"
}
```
WhatsApp groups have chat IDs ending in `@g.us`:

Media is passed as array of file paths:
- **DM**: `"1234567890@s.whatsapp.net"`
- **Group**: `"120363012345@g.us"`

```json
{
"from": "1234567890@c.us",
"body": "Photo",
"media": ["/tmp/photo.jpg"],
"type": "image"
}
```
Available policies:

### Auto-Reconnect
| Policy | Behavior |
|--------|----------|
| `open` | Accept all messages |
| `pairing` | Require pairing code approval (default for DB instances) |
| `allowlist` | Only users in `allow_from` |
| `disabled` | Reject all messages |

If bridge connection drops:
- Exponential backoff: 1s → 30s max
- Continuous retry attempts
- Logs warn on reconnect failures
Group `pairing` policy: unpaired groups receive a pairing code reply. Approve via `goclaw pairing approve <CODE>`.

## Common Patterns
### @Mention Gating

### Sending to a Chat
When `require_mention` is `true`, the bot only responds in group chats when explicitly @mentioned. Unmentioned messages are recorded for context — when the bot is mentioned, recent group history is prepended to the message.

```go
manager.SendToChannel(ctx, "whatsapp", "1234567890@c.us", "Hello!")
```
Fails closed — if the bot's JID is unknown, messages are ignored.

### Checking if Chat is a Group
### Media Support

```go
isGroup := strings.HasSuffix(chatID, "@g.us")
```
GoClaw downloads incoming media directly (images, video, audio, documents, stickers) to temporary files, then passes them to the agent pipeline.

Supported inbound media types: image, video, audio, document, sticker (max 20 MB each).

Outbound media: GoClaw uploads files to WhatsApp's servers with proper encryption. Supports image, video, audio, and document types with captions.

### Message Formatting

LLM output is converted from Markdown to WhatsApp's native formatting:

| Markdown | WhatsApp | Rendered |
|----------|----------|----------|
| `**bold**` | `*bold*` | **bold** |
| `_italic_` | `_italic_` | _italic_ |
| `~~strikethrough~~` | `~strikethrough~` | ~~strikethrough~~ |
| `` `inline code` `` | `` `inline code` `` | `code` |
| `# Header` | `*Header*` | **Header** |
| `[text](url)` | `text url` | text url |
| `- list item` | `• list item` | • list item |

Fenced code blocks are preserved as ` ``` `. HTML tags from LLM output are pre-processed to Markdown equivalents before conversion. Long messages are automatically chunked at ~4096 characters, splitting at paragraph or line boundaries.

### Typing Indicators

GoClaw shows "typing..." in WhatsApp while the agent processes a message. WhatsApp clears the indicator after ~10 seconds, so GoClaw refreshes every 8 seconds until the reply is sent.

### Auto-Reconnect

Reconnection is handled automatically. If the connection drops:
- Built-in reconnect logic handles retry with exponential backoff
- Channel health status updated (degraded → healthy on reconnect)
- No manual reconnect loop needed

### LID Addressing

WhatsApp uses dual identity: phone JID (`@s.whatsapp.net`) and LID (`@lid`). Groups may use LID addressing. GoClaw normalizes to phone JID for consistent policy checks, pairing lookups, and allowlists.

## Troubleshooting

| Issue | Solution |
|-------|----------|
| "Connection refused" | Verify bridge is running. Check `bridge_url` is correct and accessible. |
| "WebSocket: close normal closure" | Bridge shut down gracefully. Restart bridge service. |
| Continuous reconnect attempts | Bridge is down or unreachable. Check bridge logs. |
| Messages not received | Verify bridge is receiving WhatsApp events. Check bridge logs. |
| Group detection fails | Ensure chat ID ends with `@g.us` for groups, `@c.us` for DMs. |
| Media not sent | Ensure file paths are accessible to bridge. Check bridge supports media. |
| No QR code appears | Check GoClaw logs. Ensure the server can reach WhatsApp servers (ports 443, 5222). |
| QR scanned but no auth | Auth state may be corrupted. Use "Re-authenticate" button or restart the channel. |
| Messages not received | Check `dm_policy` and `group_policy`. If `pairing`, the user/group needs approval via `goclaw pairing approve`. |
| Media not received | Check GoClaw logs for "media download failed". Ensure temp directory is writable. Max 20 MB per file. |
| Typing indicator stuck | GoClaw auto-cancels typing when reply is sent. If stuck, WhatsApp connection may have dropped — check channel health. |
| Group messages ignored | Check `group_policy`. If `pairing`, the group needs approval. If `require_mention` is true, @mention the bot. |
| "logged out" in logs | WhatsApp revoked the session. Use "Re-authenticate" button to scan a new QR code. |
| `bridge_url` error on startup | `bridge_url` is no longer supported. WhatsApp now runs natively — remove `bridge_url` from config/credentials. |

## Migrating from Bridge

If you previously used the Baileys bridge (`bridge_url` config):

1. Remove `bridge_url` from your channel config or credentials
2. Remove/stop the bridge container (no longer needed)
3. Delete the bridge shared volume (`wa_media`)
4. Re-authenticate via QR scan in the UI (existing bridge auth state is not compatible)

GoClaw will detect old `bridge_url` config and show a clear migration error.

## What's Next

Expand All @@ -144,4 +166,4 @@ isGroup := strings.HasSuffix(chatID, "@g.us")
- [Larksuite](/channel-feishu) — Larksuite integration
- [Browser Pairing](/channel-browser-pairing) — Pairing flow

<!-- goclaw-source: a47d7f9f | updated: 2026-03-31 -->
<!-- goclaw-source: whatsapp-direct | updated: 2026-04-07 -->
14 changes: 8 additions & 6 deletions getting-started/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -572,20 +572,22 @@ Docker-based isolation for code execution. Can be set globally or overridden per
```jsonc
"whatsapp": {
"enabled": true,
"bridge_url": "http://localhost:8080",
"allow_from": [],
"dm_policy": "open",
"group_policy": "disabled",
"dm_policy": "pairing",
"group_policy": "pairing",
"require_mention": false,
"history_limit": 200,
"block_reply": false
}
```

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `bridge_url` | string | — | WhatsApp bridge service URL |
| `allow_from` | []string | — | Allowlisted phone numbers/JIDs |
| `dm_policy` | string | `"open"` | DM access policy |
| `group_policy` | string | `"disabled"` | Group access policy |
| `dm_policy` | string | `"pairing"` | DM access policy |
| `group_policy` | string | `"pairing"` (DB) / `"open"` (config) | Group access policy |
| `require_mention` | bool | `false` | Only respond in groups when @mentioned |
| `history_limit` | int | `200` | Max pending group messages for context |
| `block_reply` | bool | `false` | Suppress intermediate replies |

### Zalo
Expand Down
7 changes: 6 additions & 1 deletion reference/config-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -272,7 +272,12 @@ Messaging channel configuration.
| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `enabled` | boolean | `false` | Enable WhatsApp channel |
| `bridge_url` | string | — | WhatsApp bridge service URL |
| `allow_from` | string[] | — | Allowlist of user/group JIDs |
| `dm_policy` | string | `"pairing"` | `"pairing"`, `"open"`, `"allowlist"`, `"disabled"` |
| `group_policy` | string | `"pairing"` (DB) / `"open"` (config) | `"open"`, `"pairing"`, `"allowlist"`, `"disabled"` |
| `require_mention` | boolean | `false` | Only respond in groups when @mentioned |
| `history_limit` | int | `200` | Max pending group messages for context (0=disabled) |
| `block_reply` | boolean | — | Override gateway block_reply (nil=inherit) |

### `channels.slack`

Expand Down
2 changes: 1 addition & 1 deletion reference/environment-variables.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Setting a token/credential via environment auto-enables that channel.
| `GOCLAW_LARK_APP_SECRET` | Feishu/Lark | App secret |
| `GOCLAW_LARK_ENCRYPT_KEY` | Feishu/Lark | Event encryption key |
| `GOCLAW_LARK_VERIFICATION_TOKEN` | Feishu/Lark | Event verification token |
| `GOCLAW_WHATSAPP_BRIDGE_URL` | WhatsApp | Bridge service URL |
| `GOCLAW_WHATSAPP_ENABLED` | WhatsApp | Enable WhatsApp channel (`true`/`false`) |

---

Expand Down
20 changes: 10 additions & 10 deletions troubleshooting/channels.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,19 +95,19 @@ Zalo OA Bot is **DM only** (no group chats) with a 2000-character text limit per

## WhatsApp

WhatsApp uses a **bridge pattern** — GoClaw connects to an external bridge (e.g., mautrix-whatsapp) over WebSocket. GoClaw does not connect directly to WhatsApp.
WhatsApp connects **directly** via native multi-device protocol. No external bridge service required. Auth state is stored in the database (PostgreSQL/SQLite).

| Problem | Cause | Solution |
|---------|-------|----------|
| `whatsapp bridge_url is required` | Bridge URL not set | Set `GOCLAW_WHATSAPP_BRIDGE_URL` |
| `dial whatsapp bridge ...: ...` | Bridge not running or wrong URL | Start your bridge service; verify URL and port |
| `initial whatsapp bridge connection failed, will retry` | Bridge not ready yet | Transient — GoClaw retries automatically |
| `whatsapp bridge not connected` on send | Message attempted before bridge connected | Wait for bridge startup; check logs for reconnect messages |
| `invalid whatsapp message JSON` | Bridge version mismatch | Update bridge to expected message format |

**Required env var:** `GOCLAW_WHATSAPP_BRIDGE_URL=ws://localhost:29318`

The bridge must be separately authenticated with WhatsApp (QR scan via bridge UI) before GoClaw can send/receive messages.
| No QR code appears | Server can't reach WhatsApp | Check network connectivity (ports 443, 5222) |
| QR scanned but no auth | Auth state corrupted | Use "Re-authenticate" in UI or restart channel |
| `whatsapp bridge_url is required` | Old config still present | Remove `bridge_url` from config/credentials — no longer needed |
| `whatsapp not connected` on send | Not yet authenticated | Scan QR code via UI wizard first |
| `logged out` in logs | WhatsApp revoked session | Use "Re-authenticate" button to scan new QR |
| Group messages ignored | Policy or mention gate | Check `group_policy` and `require_mention` settings |
| Media download failed | Network or file issue | Check logs; verify temp dir writable; max 20 MB per file |

Authentication is done via QR scan in the GoClaw UI (Channels > WhatsApp > Re-authenticate).

---

Expand Down
Loading