Skip to content
Draft
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
2 changes: 1 addition & 1 deletion .claude/skills/goclaw-docs-audit/mapping.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
{"pattern": "internal/channels/telegram/*", "docs": ["channels/telegram.md", "troubleshooting/channels.md"], "priority": "medium"},
{"pattern": "internal/channels/discord/*", "docs": ["channels/discord.md", "troubleshooting/channels.md"], "priority": "medium"},
{"pattern": "internal/channels/feishu/*", "docs": ["channels/feishu.md", "channels/larksuite.md", "troubleshooting/channels.md"], "priority": "medium"},
{"pattern": "internal/channels/zalo/*", "docs": ["channels/zalo-oa.md", "channels/zalo-personal.md", "troubleshooting/channels.md"], "priority": "medium"},
{"pattern": "internal/channels/zalo/*", "docs": ["channels/zalo-bot.md", "channels/zalo-oa.md", "channels/zalo-personal.md", "troubleshooting/channels.md"], "priority": "medium"},
{"pattern": "internal/channels/whatsapp/*", "docs": ["channels/whatsapp.md", "troubleshooting/channels.md"], "priority": "medium"},
{"pattern": "internal/channels/pancake/*", "docs": ["channels/pancake.md", "troubleshooting/channels.md"], "priority": "high"},
{"pattern": "internal/channels/facebook/*", "docs": ["channels/facebook.md", "troubleshooting/channels.md"], "priority": "medium"},
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
- [Discord](channels/discord.md)
- [Feishu / Lark](channels/feishu.md)
- [Larksuite](channels/larksuite.md)
- [Zalo Bot](channels/zalo-bot.md)
- [Zalo OA](channels/zalo-oa.md)
- [Zalo Personal](channels/zalo-personal.md)
- [Slack](channels/slack.md)
Expand Down Expand Up @@ -182,7 +183,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for writing guidelines and bilingual proc
├── core-concepts/ # Architecture & concepts (6 pages)
├── agents/ # Agent configuration (8 pages)
├── providers/ # LLM provider guides (25 pages)
├── channels/ # Messaging channel setup (14 pages)
├── channels/ # Messaging channel setup (15 pages)
├── agent-teams/ # Team collaboration (6 pages)
├── advanced/ # Power-user features (26 pages)
├── deployment/ # Deploy & operate (7 pages)
Expand Down
52 changes: 31 additions & 21 deletions channels/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,26 +9,27 @@ Complete documentation for all messaging platform integrations in GoClaw.
3. **[Discord](./discord.md)** — Gateway API, placeholder editing, threads
4. **[Slack](./slack.md)** — Socket Mode, threads, streaming, reactions, debounce
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)** — 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
6. **[Zalo Bot](./zalo-bot.md)** — Static-token bot, DM-only, polling/webhook
7. **[Zalo OA](./zalo-oa.md)** — Official Account (OAuth v4), polling/webhook, pairing, images
8. **[Zalo Personal](./zalo-personal.md)** — Personal account (unofficial), DM + groups
9. **[WhatsApp](./whatsapp.md)** — Direct connection, QR auth, media, typing indicators, pairing
10. **[WebSocket](./websocket.md)** — Direct RPC, custom clients, streaming events
11. **[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 | 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 | WA native | JSON |
| **Reactions** | Yes | -- | Yes | Yes | -- | -- | -- | -- |
| **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 |
| Feature | Telegram | Discord | Slack | Larksuite | Zalo Bot | Zalo OA | Zalo Pers | WhatsApp | WebSocket |
|---------|----------|---------|-------|--------|---------|---------|-----------|----------|-----------|
| **Setup Complexity** | Easy | Easy | Easy | Medium | Easy | Medium | Hard | Medium | Very Easy |
| **Transport** | Polling | Gateway | Socket Mode | WS/Webhook | Polling/Webhook | Polling/Webhook | Protocol | Direct connection | WebSocket |
| **DM Support** | Yes | Yes | Yes | Yes | Yes | Yes | Yes | Yes | N/A |
| **Group Support** | Yes | Yes | Yes | Yes | No | No | Yes | Yes | N/A |
| **Streaming** | Yes | Yes | Yes | Yes | No | No | No | No | Yes |
| **Rich Format** | HTML | Markdown | mrkdwn | Cards | Plain | Plain | Plain | WA native | JSON |
| **Reactions** | Yes | -- | Yes | Yes | -- | Yes (off/min/full) | -- | -- | -- |
| **Media** | Photos, Voice, Files | Files, Embeds | Files (20MB) | Images, Files | Images | Images | -- | Images, Video, Audio, Docs | N/A |
| **Auth Method** | Token | Token | 3 Tokens | App ID + Secret | Bot Token | OAuth v4 (App ID + Secret + Redirect) | Credentials | QR Code | Token + Pairing |
| **Risk Level** | Low | Low | Low | Low | Low | Low | High | Medium | Low |

## Configuration Files

Expand All @@ -42,6 +43,7 @@ All channel config lives in the root `config.json`:
"slack": { ... },
"feishu": { ... },
"zalo": { ... },
"zalo_oa": { ... },
"zalo_personal": { ... },
"whatsapp": { ... }
}
Expand Down Expand Up @@ -126,12 +128,20 @@ Flexible format supporting:
- [ ] If webhook: Set URL in Larksuite console
- [ ] Enable in config

### Zalo Bot

- [ ] Create / open bot at developers.zalo.me Bot API
- [ ] Copy access token
- [ ] Enable in config (`channels.zalo`) with polling by default
- [ ] Optionally switch to webhook with `transport: "webhook"` + secret

### Zalo OA

- [ ] Create Official Account at oa.zalo.me
- [ ] Enable Bot API
- [ ] Copy API key
- [ ] Enable in config (polling by default)
- [ ] Verify gateway domain on developers.zalo.me
- [ ] Set OA Callback URL on Zalo console
- [ ] Copy App ID + Secret Key
- [ ] Run GoClaw OAuth wizard with App ID, Secret, Redirect URI
- [ ] Enable in config (polling by default; switch to webhook bootstrap flow if needed)

### Zalo Personal

Expand Down
217 changes: 217 additions & 0 deletions channels/zalo-bot.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
# Zalo Bot Channel

Static-token Zalo Bot integration. Single-credential setup, polling by default, DM-only — the simplest way to bring a Zalo bot online when you don't need OAuth.

## Overview

`zalo_bot` is the static-token variant of GoClaw's Zalo channel family. Operators paste a single bot access token from the Zalo developer console and the channel is online — no OAuth flow, no refresh cycle, no domain verification.

> **Heads up —** Zalo Bot Platform (`bot.zapps.me`) is **newer than the OA API** and the surface is **still expanding** — sticker support and typing actions both landed recently. Endpoints you don't see today may show up later; re-check the [Bot API docs](https://bot.zapps.me/docs/) before assuming a feature is missing.

This is the right pick for dev / test, internal bots, low-volume DM use cases, or any setup where you don't have (or don't need) a full Zalo Official Account.

## Choosing your Zalo variant

GoClaw exposes three Zalo channel types. They differ in coverage, supportability, and risk — pick by what your deployment can tolerate.

| | **Zalo OA** | **Zalo Bot** *(this page)* | **Zalo Personal** |
|---|---|---|---|
| **Officially supported** | Yes — Zalo OA API | Yes — `bot.zapps.me` (newer platform, surface still expanding) | **No** — reverse-engineered protocol |
| **Auth** | OAuth v4 (App ID + Secret + consent) | Static bot token | Account credentials |
| **Account type** | Verified Official Account (business) | Bot identity tied to a developer | Personal Zalo account |
| **DMs** | Yes | Yes | Yes |
| **Groups** | No | No | **Yes** |
| **Multi-account per instance** | Yes (multi-OA) | One bot per channel | One account per channel |
| **Token rotation** | Auto (1h access / 90d single-use refresh) | None — static token | Manual re-login when sessions die |
| **Message types** | Full OA suite (text, image, file, list, button, …) | Text, image, sticker, typing — and growing | Broad (whatever the protocol exposes) |
| **Image upload cap** | 1 MB | 5 MB (GoClaw cap) | -- |
| **Quotas** | OA tier (broadcast caps; transactional within 7 days of user msg) | Bot platform quotas | Account-level, informal |
| **Account ban risk** | None — first-party API | None — first-party API | **High** — Zalo can lock the account at any time |
| **Best for** | Production CS / business messaging at scale | Lightweight bots, internal tools, dev / test, low-volume DM | Personal or group automation where no OA exists, accepting ban risk |

> **About Zalo Personal —** uses a reverse-engineered protocol; GoClaw logs `security.unofficial_api` on startup. **Not for production.** See [Zalo Personal](/channel-zalo-personal) for the full risk profile.

**Quick decision** — Have an OA → [Zalo OA](/channel-zalo-oa). No OA but a bot is enough → **Zalo Bot**. Need groups, willing to accept ban risk → [Zalo Personal](/channel-zalo-personal).

## Getting Your Bot Token

1. Go to `developers.zalo.me` and open the app that owns your bot.
2. Navigate to the Bot API section (URL pattern: `developers.zalo.me/app/<app_id>/bot-api`; check the latest Zalo dev docs if the menu has moved).
3. Create a new bot (or open an existing one) and copy the **access token** — it's an alphanumeric string.

The token does not expire automatically the way OAuth refresh tokens do, so you can paste it once and forget about it. If you regenerate the token in the Zalo console, update GoClaw's Credentials tab to match.

## GoClaw Setup

In the GoClaw web UI go to **Channels → Add Channel → Zalo Bot**. The wizard asks for one value:

| Field | What to paste |
|-------|---------------|
| **Token** | Bot access token from the Zalo developer console |

That's it for credentials. Optional runtime knobs (DM policy, transport, webhook secret) live in the channel detail page after creation.

```json
{
"channels": {
"zalo": {
"enabled": true,
"token": "YOUR_BOT_TOKEN",
"dm_policy": "pairing",
"media_max_mb": 5
}
}
}
```

> **Note —** the config block uses the historical key `zalo` for backwards compatibility with the static-token channel. The `zalo_oa` block in `config.json` is the OAuth variant.

## Configuration

All config keys live under `channels.zalo`:

| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `enabled` | bool | `false` | Enable / disable the channel |
| `token` | string | required | Bot access token from Zalo console |
| `dm_policy` | string | `"pairing"` | `pairing`, `allowlist`, `open`, `disabled` |
| `allow_from` | list | `[]` | User ID / username allowlist |
| `transport` | string | `"polling"` | `polling` (default) or `webhook` |
| `webhook_path` | string | -- | Override auto-derived routing slug |
| `webhook_secret` | string | -- | Required when `transport: "webhook"` |
| `media_max_mb` | int | `5` | Max image upload size (MB) |
| `block_reply` | bool | -- | Override gateway `block_reply` (nil = inherit) |

## Ingestion Modes

The channel runs in exactly one mode per instance:

| Mode | When to use | What runs |
|------|-------------|-----------|
| **Polling** *(default)* | No public HTTPS endpoint | Long-polling `getUpdates` calls |
| **Webhook** *(opt-in)* | Lower latency, public endpoint available | Zalo POSTs to `/channels/zalo/webhook/<slug>` |

Both produce equivalent inbound message shapes — switching transports does not change agent behavior.

## Webhook Mode

Set `transport: "webhook"` and supply a `webhook_secret`. Then register the URL on the Zalo bot console.

```json
{
"channels": {
"zalo": {
"transport": "webhook",
"webhook_path": "support-bot",
"webhook_secret": "PASTE_FROM_ZALO_CONSOLE"
}
}
}
```

### Signature verification

Zalo Bot API uses a constant-token header — different from the OA variant's HMAC scheme.

| Property | Value |
|----------|-------|
| Header | `X-Bot-Api-Secret-Token` |
| Compare | Constant-time string match |
| Empty secret | Channel rejects start (Bot API mandates a secret in webhook mode) |
| Replay window | None — bot tokens are long-lived, no timestamp window |

The handler also drops events where `from.id == botID` so the agent doesn't react to its own outbound replies (Zalo redelivers sends through the same webhook).

### Slug rules

Slugs are auto-derived from the channel name. Override via `webhook_path`. Same conventions as Zalo OA: lowercase letters / digits / hyphens, length 2–63, must start with `[a-z0-9]`, reserved words `webhook`, `zalo`, `_health`, `_metrics` rejected.

## Polling Mode

Polling is the default and uses Zalo's `getUpdates` long-polling endpoint. The defaults are tuned for the Bot API and are not operator-configurable.

| Property | Value |
|----------|-------|
| Long-poll timeout | 30 seconds |
| Error backoff | 5 seconds |
| Read marker | Zalo marks downloaded messages as read |

The channel keeps an offset cursor and resumes after restart from the last seen message.

## Text & Media

| Constraint | Value |
|------------|-------|
| Outbound text | 2,000 characters per message (Zalo Bot API limit) |
| Image formats | JPG, PNG |
| Image max size | 5 MB (override via `media_max_mb`) |
| Inbound media | Downloaded to temp files during processing |
| Outbound media | Attached as `media` array in send payload |

```json
{
"channel": "zalo",
"content": "Here's your image",
"media": [
{ "url": "/tmp/image.jpg", "type": "image" }
]
}
```

## DM-Only Restriction

Zalo Bot API does not expose group messaging, so the channel is DM-only:

- All inbound is routed as DMs — no `group_id` / `thread_id` plumbing
- No `@bot` mention detection (the API doesn't surface mentions)
- No "broadcast to all followers" mode — GoClaw deliberately doesn't expose it
- Per-group config blocks (like Telegram has) are not applicable

If your use case needs groups, use [Zalo Personal](/channel-zalo-personal) at the cost of running on reverse-engineered protocol.

## Pairing Code Flow

`dm_policy: "pairing"` is the default. New users get a pairing prompt, debounced to avoid spam:

| Behavior | Value |
|----------|-------|
| Prompt frequency | Once per user, then `60s` debounce on subsequent unrecognized contacts |
| Approval syntax | `/pair CODE` (sent by the operator/owner in any DM) |
| Operator visibility | Pairing instructions surface in gateway logs |

Other DM policies:

- `open` — accept all DMs (public bots)
- `allowlist` — only IDs / usernames in `allow_from`
- `disabled` — no DMs at all

## Troubleshooting

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| `Invalid token` from Zalo | Token typoed, regenerated, or bot disabled | Re-copy from Zalo console; confirm bot is enabled |
| No inbound messages | Polling stalled, bot suspended, or network issue | Check logs for `zalo.poll.error`; verify network reachability |
| Webhook signature mismatch | Token doesn't match Zalo console value | Re-copy and re-save; whitespace counts |
| Webhook receives nothing | URL not registered on Zalo console, or bot disabled | Re-register URL; verify bot status |
| Pairing prompt never sends | `dm_policy` not `"pairing"`, or operator can't DM the bot | Switch policy; check operator permissions |

### Bot API error code reference

| Code | Meaning | What to check |
| --- | --- | --- |
| `400` | Bad request — invalid path or API name | Inspect outbound payload; usually a missing required field |
| `401` | Unauthorized — token expired or invalid, or webhook signature mismatch | Re-paste token / `webhook_secret` in Credentials tab |
| `403` | Internal server error | Transient; retry with backoff. Alert if persistent |
| `404` | Not found — invalid resource (slug not registered or channel stopped) | Re-enable channel; verify URL on Zalo bot console |
| `408` | Request timeout — long-poll idle window | Normal during low-traffic periods; ignore |
| `429` | Quota exceeded | Throttle outbound; check Bot API quota tier |

Source: <https://bot.zapps.me/docs/error-code/>. The Bot API response always includes a `description` field — check it for the specific reason; the numeric code only narrows the category.

## What's Next

- [Channels overview](/channels-overview) — DM policies, pairing, message flow
- [Zalo OA](/channel-zalo-oa) — OAuth variant with auto-refresh, multi-OA
- [Zalo Personal](/channel-zalo-personal) — reverse-engineered personal account (groups supported)

<!-- goclaw-source: 8f14d2cd | updated: 2026-05-01 -->
Loading