diff --git a/.claude/skills/goclaw-docs-audit/mapping.json b/.claude/skills/goclaw-docs-audit/mapping.json index 5f3307f..0197ad8 100644 --- a/.claude/skills/goclaw-docs-audit/mapping.json +++ b/.claude/skills/goclaw-docs-audit/mapping.json @@ -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"}, diff --git a/README.md b/README.md index 6a016b7..202f92d 100644 --- a/README.md +++ b/README.md @@ -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) @@ -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) diff --git a/channels/INDEX.md b/channels/INDEX.md index 922062a..90b0b70 100644 --- a/channels/INDEX.md +++ b/channels/INDEX.md @@ -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 @@ -42,6 +43,7 @@ All channel config lives in the root `config.json`: "slack": { ... }, "feishu": { ... }, "zalo": { ... }, + "zalo_oa": { ... }, "zalo_personal": { ... }, "whatsapp": { ... } } @@ -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 diff --git a/channels/zalo-bot.md b/channels/zalo-bot.md new file mode 100644 index 0000000..8fb44fd --- /dev/null +++ b/channels/zalo-bot.md @@ -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//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/` | + +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: . 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) + + diff --git a/channels/zalo-oa.md b/channels/zalo-oa.md index 5c2a1c0..ec89f00 100644 --- a/channels/zalo-oa.md +++ b/channels/zalo-oa.md @@ -1,118 +1,267 @@ # Zalo OA Channel -Zalo Official Account (OA) integration. DM-only with pairing-based access control and image support. +Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. -## Setup +## Overview -**Create Zalo OA:** +`zalo_oa` is the OAuth v4 variant of GoClaw's Zalo channel family. Operators connect a verified Zalo Official Account via the standard Zalo developer console; the gateway stores an encrypted refresh token and rotates access tokens automatically. Inbound messages reach the agent through one of two transports — long polling against `listrecentchat` (default) or webhook events POSTed by Zalo. -1. Go to https://oa.zalo.me -2. Create Official Account (requires Zalo phone number) -3. Set up OA name, avatar, and cover photo -4. In OA settings, go to "Settings" → "API" → "Bot API" -5. Create API key -6. Copy API key for configuration +This channel is meant for production deployments and supports multiple OAs per instance. -**Enable Zalo OA:** +## Choosing your Zalo variant -```json -{ - "channels": { - "zalo": { - "enabled": true, - "token": "YOUR_API_KEY", - "dm_policy": "pairing", - "allow_from": [], - "media_max_mb": 5 - } - } -} -``` +GoClaw exposes three Zalo channel types. They differ in coverage, supportability, and risk — pick by what your deployment can tolerate. -## Configuration +| | **Zalo OA** *(this page)* | **Zalo Bot** | **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 | -All config keys are in `channels.zalo`: +> **About Zalo Bot —** the platform (`bot.zapps.me`) is newer than the OA API and Zalo is actively adding endpoints (sticker and typing actions landed recently). Treat it as evolving — re-check the [Bot API docs](https://bot.zapps.me/docs/) before assuming a feature is missing. +> +> **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. -| Key | Type | Default | Description | -|-----|------|---------|-------------| -| `enabled` | bool | false | Enable/disable channel | -| `token` | string | required | API key from Zalo OA console | -| `allow_from` | list | -- | User ID allowlist | -| `dm_policy` | string | `"pairing"` | `pairing`, `allowlist`, `open`, `disabled` | -| `webhook_url` | string | -- | Optional webhook URL (override polling) | -| `webhook_secret` | string | -- | Optional webhook signature secret | -| `media_max_mb` | int | 5 | Max image file size (MB) | -| `block_reply` | bool | -- | Override gateway block_reply (nil=inherit) | +**Quick decision** — Have an OA → **Zalo OA**. No OA but a bot is enough → [Zalo Bot](/channel-zalo-bot). Need groups, willing to accept ban risk → [Zalo Personal](/channel-zalo-personal). -## Features +## Prerequisites -### DM-Only +Two things to know first: -Zalo OA only supports direct messaging. Group functionality is not available. All messages are treated as DMs. +- **App** vs **OA** — an *app* (registered at `developers.zalo.me`) holds the App ID and Secret Key. An *OA* (created at `oa.zalo.me`) is the business account users follow. One app can be linked to multiple OAs. +- **Vietnamese phone number** — required to register both the app and the OA. -### Long Polling +Then complete these on `developers.zalo.me`: -Default mode: Bot polls Zalo API every 30 seconds for new messages. Server returns messages and marks them read. +1. **Create / activate** the app and copy **App ID** + **Secret Key** (the OAuth secret, not the webhook secret). +2. **Link OA** — open the **Liên kết OA** tab and link the Official Account you want this channel to drive. +3. **Verify your gateway domain** (HTML meta tag or DNS TXT). Wait until it appears in **Danh sách domain xác thực**. +4. **Set the OA Callback URL** to the same value you'll paste into GoClaw's Redirect URI field — they must match exactly. +5. **Request permissions** — open the app's Permissions tab and request: `Quản lý tin nhắn` (messages), `Quản lý người quan tâm` (followers), `Upload`, and `Webhook` (only if you'll use webhook mode). Approval can take 1–3 business days. Without these, API calls fail with `-216`. -- Poll timeout: 30 seconds (default) -- Error backoff: 5 seconds -- Text limit: 2,000 characters per message -- Image limit: 5 MB +> **Heads up —** Zalo's `-14003` means the domain isn't verified yet or the callback URL doesn't match what you registered. `-216` means a permission group hasn't been granted to your app. -### Webhook Mode (Optional) +## GoClaw Setup Wizard -Instead of polling, configure Zalo to POST events to your gateway: +In the GoClaw web UI go to **Channels → Add Channel → Zalo OA**. The wizard asks for three values: -```json -{ - "webhook_url": "https://your-gateway.com/zalo/webhook", - "webhook_secret": "your_webhook_secret" -} -``` +| Field | What to paste | +|-------|---------------| +| **App ID** | Numeric ID from `developers.zalo.me` | +| **Secret Key** | OAuth secret from the same console | +| **Redirect URI** | Same URL you set as the OA Callback URL | + +After you save, GoClaw opens the Zalo consent flow in a popup. Approve it with the Zalo account that owns the OA. On success, the OA ID is auto-discovered and stored encrypted; the channel detail page surfaces it read-only. + +The **Webhook Secret Key** field can stay empty during creation — you'll fill it in later if you switch to webhook mode (see [Webhook Mode](#webhook-mode)). + +> **About tokens —** Zalo issues a 1-hour access token plus a 90-day refresh token. The refresh token is **single-use** — every refresh returns a new one, and GoClaw rotates it automatically. If a refresh fails (e.g. token unused for 90 days, or revoked), the channel goes Degraded and the OA owner needs to re-run the consent flow. + +## Ingestion Modes + +The channel listens for inbound messages in exactly one of two modes per instance: + +| Mode | When to use | What runs | +|------|-------------|-----------| +| **Polling** *(default)* | No public HTTPS endpoint, simpler ops | Periodic `listrecentchat` calls on a timer | +| **Webhook** *(opt-in)* | Lower latency, event-driven, public endpoint available | Zalo POSTs to `/channels/zalo/webhook/` | + +Switching transports does not change agent behavior — both produce equivalent message shapes. Webhook deliveries do not fall back to polling on failure unless you explicitly enable `catch_up_on_restart`. + +## Webhook Mode + +Webhook mode is opt-in via `transport: "webhook"`. The setup uses a deliberate two-step bootstrap to get around Zalo's chicken-and-egg problem (you can't paste the secret before saving the URL, but Zalo verifies the URL before it shows you the secret). + +### Bootstrap flow + +1. Create the channel with `transport: "webhook"` and **leave `webhook_secret_key` empty**. +2. The gateway answers Zalo's verification ping with HTTP 200 — signature checking is skipped while the secret is empty (the channel is `Degraded — awaiting webhook secret` during this window). +3. Copy **Khóa bí mật OA** from the Zalo console and paste it into the channel's Credentials tab in GoClaw. +4. Save. The channel transitions to `Healthy` and signature verification activates. + +This same flow handles toggling an existing polling channel to webhook — just clear the secret first, save URL on Zalo, then paste the new secret. + +### Slug rules + +Each channel gets a routing slug derived from its name. Override it via `webhook_path` if you need a stable URL (e.g. `customer-support` instead of an auto-generated one). + +- Lowercase letters, digits, hyphens only +- Must start with `[a-z0-9]` +- Length 2–63 chars +- Reserved words rejected: `webhook`, `zalo`, `_health`, `_metrics` + +### Signature verification + +Zalo signs every event with `X-ZEvent-Signature: hex(SHA256(appID + body + timestamp + secret))`. The gateway verifies this against your saved secret and rejects mismatches. + +| Setting | Default | Purpose | +|---------|---------|---------| +| `webhook_signature_mode` | `"strict"` | Reject mismatches (production) | +| `webhook_signature_mode` | `"log_only"` | Warn but allow (testing) | +| `webhook_signature_mode` | `"disabled"` | Accept unsigned (diagnostic only — never in production) | +| `webhook_replay_window_seconds` | `300` (clamp `[60, 3600]`) | Reject events older than this | + +The handler also drops events where `sender.id == oa_id` — Zalo redelivers your outbound replies through the same webhook, and you don't want the agent to react to its own messages. + +## Polling Mode + +Polling is the default. The gateway calls `listrecentchat` on an interval and processes new messages. + +| Key | Default | Notes | +|-----|---------|-------| +| `poll_interval_seconds` | `15` | Range `[5, 120]` | +| `poll_count` | `10` | Page size; clamp `[1, 10]` (Zalo API hard cap — error `-210` above) | +| `poll_burndown_max_pages` | `10` | Pages allowed per cycle without sleep; clamp `[1, 20]`; set to `1` to disable burn-down | +| `catch_up_on_restart` | `false` | Single bounded sweep on Start; useful after long downtime | + +**Burn-down resilience** lets the gateway clear a backlog. A worst-case cycle of `10 × 10 = 100` messages happens before the next sleep. If 250 messages are pending, the burn-down empties them across 2–3 cycles instead of crawling 10 at a time. + +`catch_up_on_restart` is off by default because it can replay stale conversations on every restart. Turn it on if you want a single bounded `listrecentchat` sweep at boot, then normal polling resumes. + +When `transport: "webhook"`, all polling parameters are ignored. -Zalo sends a HMAC signature in header `X-Zalo-Signature`. Implementation verifies this before processing. +## Media Limits -### Image Support +Zalo OA enforces per-endpoint upload caps — these are server-side, not configurable by GoClaw: -Bot can receive and send images (JPG, PNG). Max 5 MB by default. +| Endpoint | Max size | Allowed types | +|----------|----------|---------------| +| Image | 1 MB | JPEG, PNG | +| File | 5 MB | PDF, DOC, DOCX | +| GIF | 5 MB | GIF | -**Receive**: Images are downloaded and stored as temporary files during message processing. +Auth header for all upload calls is `access_token: ` — not a query string. GoClaw compresses oversized images before upload; oversized files fail fast with `-210`. -**Send**: Images can be sent as media attachment: +## Quoted Replies + +`quote_user_message` is **on by default**. Outbound replies quote the user's last inbound message via Zalo's `message.quote_message_id` field — handy in busy CS threads where context matters. + +| Behavior | Default | Notes | +|----------|---------|-------| +| Quote first chunk of multi-chunk reply | Yes | Subsequent chunks are unquoted | +| Quote image / file / GIF sends | No | Zalo API restriction — silently dropped | +| Quote source > 48h or deleted | No | Auto-retried without quote; logs `zalo_oa.send.quote_dropped_payload_error` | + +Set `quote_user_message: false` to disable globally. + +## Status Reactions + +Zalo OA can surface agent progress as emoji reactions on the user's inbound message. Reactions don't count against Zalo's monthly active-message quota, and failures never affect channel health. + +| Level | What you see | +|-------|--------------| +| `off` *(default)* | No reactions | +| `minimal` | Terminal only — ❤ on success, 😢 on failure | +| `full` | Adds 👍 on first intermediate event (debounced ≤1 per 700ms) | + +The `minimal` level is recommended for customer-service OAs — `full` chews through the 50-reaction-per-message cap and looks unprofessional. Mid-run tool / coding / web statuses are deliberately not mapped to Zalo OA. The frontend wizard may show `minimal` as the suggested default; the runtime config default remains `off`. + +`ClearReaction` sends a `/-remove` sentinel since Zalo has no separate clear endpoint. + +## Common Errors + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| Zalo returns `-14003` during OAuth | Domain unverified or callback URL mismatch | Re-verify domain on `developers.zalo.me`; align Redirect URI exactly | +| Console URL-save fails | Gateway not reachable, or `>2s` to respond | Confirm public HTTPS reachability; channel must already exist in GoClaw | +| Channel stuck on `Degraded — awaiting webhook secret` | Operator skipped the secret-paste step | Open Credentials tab, paste **Khóa bí mật OA** | +| Webhook returns `401` | Signature mismatch (typo on paste) | Re-copy from Zalo console, save again | +| Webhook returns `404` | Channel stopped or slug mismatch | Re-enable channel; verify `webhook_path` matches Zalo console URL | +| No inbound events after secret saved | `webhook_signature_mode: "disabled"`, or Zalo auto-disabled webhook after 12h of non-200 retries | Restore mode to `strict`; re-save URL on Zalo console | +| Refresh token rejected (`-220` / `-118` / `invalid_grant`) | Refresh token unused for 90 days, revoked, or already consumed by another instance | OA owner re-runs OAuth consent; in multi-instance setups, ensure only one instance refreshes a given OA | +| API calls fail with `-216` | Permission group not approved for the app | Request the relevant group (`Quản lý tin nhắn`, `Upload`, etc.) on `developers.zalo.me` | +| `zalo_oa.webhook.bootstrap_drop` count growing | Events arriving during the secret-paste window | Normal during setup; resolves once secret is saved | + +### Zalo error code reference + +The most common Zalo codes you'll see in `zalo_oa.*.error` logs: + +| Code | Meaning | What to do | +| --- | --- | --- | +| `-14003` | Invalid redirect URI or unverified domain | Verify domain on `developers.zalo.me`; align Redirect URI exactly | +| `-201` | App not found / invalid params | Confirm App ID; inspect outbound payload shape | +| `-210` | Invalid `count` (must be ≤ 10) or upload size exceeded | Set `poll_count` ≤ 10; check media against [Media Limits](#media-limits) | +| `-216` | Insufficient permissions | Request the missing permission group on `developers.zalo.me` (1–3 day approval) | +| `-217` | Access token expired | Triggers automatic refresh — no operator action unless it persists | +| `-220` / `-118` | Refresh token expired or already used | OA owner must re-run OAuth consent flow | +| `100` | Invalid parameter | Check API call shape and field types | +| `110`–`112` | Recipient lookup failed; app not linked to OA | Re-link OA in Zalo console (`Liên kết OA` tab) | +| `210` | User not visible | User must follow the OA or grant friend permission | +| `2000`–`2004` | App rate-limited or temporarily disabled | Check app status; request quota increase | +| `12000`–`12012` | Quota / DND / friend-list / not-friend | Adjust outbound dispatch cadence | + +Full Social API table: . GoClaw's runtime classifies these codes internally as retriable, auth-refresh-triggering, or fatal — operators don't normally interact with that mapping; the symptom-based table above is the entry point for diagnostics. + +## Troubleshooting & Reference + +### Slog keys to watch + +``` +zalo_oa.webhook.event_received +zalo_oa.webhook.bootstrap_drop +zalo_oa.poll.burndown_capped +zalo_oa.send.quote_dropped_payload_error +zalo_webhook.handler_error +zalo_webhook.empty_message_id_streak +security.zalo_webhook_signature_mismatch +``` + +### Tracing + +Set `GOCLAW_ZALO_OA_TRACE=1` to dump raw Zalo response bodies at Debug level. **PII-sensitive** — never enable in production. + +### Polling config example ```json { - "channel": "zalo", - "content": "Here's your image", - "media": [ - { "url": "/tmp/image.jpg", "type": "image" } - ] + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "polling", + "poll_interval_seconds": 15, + "poll_count": 10, + "poll_burndown_max_pages": 10, + "reaction_level": "minimal", + "quote_user_message": true, + "dm_policy": "pairing" + } + } } ``` -### Pairing by Default - -Default DM policy is `"pairing"`. New users see pairing code instructions with 60-second debounce (no spam). Owner approves via: +### Webhook config example +```json +{ + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "webhook", + "webhook_path": "customer-support", + "webhook_signature_mode": "strict", + "webhook_replay_window_seconds": 300, + "reaction_level": "minimal", + "dm_policy": "pairing" + } + } +} ``` -/pair CODE -``` - -## Troubleshooting -| Issue | Solution | -|-------|----------| -| "Invalid API key" | Check token from Zalo OA console. Ensure OA is active and Bot API enabled. | -| No messages received | Verify polling is running (check logs). Ensure OA can accept messages (not suspended). | -| Image upload fails | Verify image file exists and is under `media_max_mb`. Check file format (JPG/PNG). | -| Webhook signature mismatch | Ensure `webhook_secret` matches Zalo console. Check timestamp is recent. | -| Pairing codes not sent | Check DM policy is `"pairing"`. Verify owner can send messages to OA. | +Credentials (`app_id`, `secret_key`, `redirect_uri`, `webhook_secret_key`) are stored encrypted via the channel's Credentials tab — they are never written to `config.json`. ## What's Next -- [Overview](/channels-overview) — Channel concepts and policies -- [Zalo Personal](/channel-zalo-personal) — Personal Zalo account integration -- [Telegram](/channel-telegram) — Telegram bot setup -- [Browser Pairing](/channel-browser-pairing) — Pairing flow +- [Channels overview](/channels-overview) — DM policies, pairing, message flow +- [Zalo Bot](/channel-zalo-bot) — static-token alternative for small deployments +- [Zalo Personal](/channel-zalo-personal) — reverse-engineered personal account - + diff --git a/index.html b/index.html index b426a17..db9bb56 100644 --- a/index.html +++ b/index.html @@ -177,6 +177,7 @@ Discord Feishu / Lark Larksuite + Zalo Bot Zalo OA Zalo Personal Slack diff --git a/js/docs-app.js b/js/docs-app.js index 30c0897..e3e915b 100644 --- a/js/docs-app.js +++ b/js/docs-app.js @@ -155,6 +155,7 @@ const DOC_MAP = { 'channel-discord': docEntry('channels', 'discord', 'Discord', 'Channel Discord', 'Discord 频道'), 'channel-feishu': docEntry('channels', 'feishu', 'Feishu / Lark', 'Channel Feishu', '飞书 / Lark'), 'channel-larksuite': docEntry('channels', 'larksuite', 'Larksuite', 'Channel Larksuite', 'Larksuite 频道'), + 'channel-zalo-bot': docEntry('channels', 'zalo-bot', 'Zalo Bot', 'Channel Zalo Bot', 'Zalo Bot 频道'), 'channel-zalo-oa': docEntry('channels', 'zalo-oa', 'Zalo OA', 'Channel Zalo OA', 'Zalo OA 频道'), 'channel-zalo-personal': docEntry('channels', 'zalo-personal', 'Zalo Personal', 'Channel Zalo Personal', 'Zalo Personal 频道'), 'channel-slack': docEntry('channels', 'slack', 'Slack', 'Channel Slack', 'Slack 频道'), diff --git a/llms-full.txt b/llms-full.txt index b0cd28e..979568e 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -9168,87 +9168,155 @@ The `group:fs` prefix selects all tools in the `fs` (Feishu/Lark) tool group. Th --- -# Zalo OA Channel +# Zalo Bot Channel -Zalo Official Account (OA) integration. DM-only with pairing-based access control and image support. +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. -## Setup +## 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 | -**Create Zalo OA:** +> **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. -1. Go to https://oa.zalo.me -2. Create Official Account (requires Zalo phone number) -3. Set up OA name, avatar, and cover photo -4. In OA settings, go to "Settings" → "API" → "Bot API" -5. Create API key -6. Copy API key for configuration +**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). -**Enable Zalo OA:** +## 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//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_API_KEY", + "token": "YOUR_BOT_TOKEN", "dm_policy": "pairing", - "allow_from": [], "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 are in `channels.zalo`: +All config keys live under `channels.zalo`: | Key | Type | Default | Description | |-----|------|---------|-------------| -| `enabled` | bool | false | Enable/disable channel | -| `token` | string | required | API key from Zalo OA console | -| `allow_from` | list | -- | User ID allowlist | +| `enabled` | bool | `false` | Enable / disable the channel | +| `token` | string | required | Bot access token from Zalo console | | `dm_policy` | string | `"pairing"` | `pairing`, `allowlist`, `open`, `disabled` | -| `webhook_url` | string | -- | Optional webhook URL (override polling) | -| `webhook_secret` | string | -- | Optional webhook signature secret | -| `media_max_mb` | int | 5 | Max image file size (MB) | -| `block_reply` | bool | -- | Override gateway block_reply (nil=inherit) | - -## Features +| `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) | -### DM-Only +## Ingestion Modes -Zalo OA only supports direct messaging. Group functionality is not available. All messages are treated as DMs. +The channel runs in exactly one mode per instance: -### Long Polling +| 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/` | -Default mode: Bot polls Zalo API every 30 seconds for new messages. Server returns messages and marks them read. +Both produce equivalent inbound message shapes — switching transports does not change agent behavior. -- Poll timeout: 30 seconds (default) -- Error backoff: 5 seconds -- Text limit: 2,000 characters per message -- Image limit: 5 MB +## Webhook Mode -### Webhook Mode (Optional) - -Instead of polling, configure Zalo to POST events to your gateway: +Set `transport: "webhook"` and supply a `webhook_secret`. Then register the URL on the Zalo bot console. ```json { - "webhook_url": "https://your-gateway.com/zalo/webhook", - "webhook_secret": "your_webhook_secret" + "channels": { + "zalo": { + "transport": "webhook", + "webhook_path": "support-bot", + "webhook_secret": "PASTE_FROM_ZALO_CONSOLE" + } + } } ``` -Zalo sends a HMAC signature in header `X-Zalo-Signature`. Implementation verifies this before processing. +### 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 -### Image Support +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. -Bot can receive and send images (JPG, PNG). Max 5 MB by default. +| 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. -**Receive**: Images are downloaded and stored as temporary files during message processing. +## Text & Media -**Send**: Images can be sent as media attachment: +| 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 { @@ -9260,30 +9328,331 @@ Bot can receive and send images (JPG, PNG). Max 5 MB by default. } ``` -### Pairing by Default +## 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: . 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) + + + +--- + +# Zalo OA Channel + +Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. + +## Overview + +`zalo_oa` is the OAuth v4 variant of GoClaw's Zalo channel family. Operators connect a verified Zalo Official Account via the standard Zalo developer console; the gateway stores an encrypted refresh token and rotates access tokens automatically. Inbound messages reach the agent through one of two transports — long polling against `listrecentchat` (default) or webhook events POSTed by Zalo. + +This channel is meant for production deployments and supports multiple OAs per instance. + +## 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** *(this page)* | **Zalo Bot** | **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 Bot —** the platform (`bot.zapps.me`) is newer than the OA API and Zalo is actively adding endpoints (sticker and typing actions landed recently). Treat it as evolving — re-check the [Bot API docs](https://bot.zapps.me/docs/) before assuming a feature is missing. +> +> **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**. No OA but a bot is enough → [Zalo Bot](/channel-zalo-bot). Need groups, willing to accept ban risk → [Zalo Personal](/channel-zalo-personal). + +## Prerequisites + +Two things to know first: + +- **App** vs **OA** — an *app* (registered at `developers.zalo.me`) holds the App ID and Secret Key. An *OA* (created at `oa.zalo.me`) is the business account users follow. One app can be linked to multiple OAs. +- **Vietnamese phone number** — required to register both the app and the OA. + +Then complete these on `developers.zalo.me`: + +1. **Create / activate** the app and copy **App ID** + **Secret Key** (the OAuth secret, not the webhook secret). +2. **Link OA** — open the **Liên kết OA** tab and link the Official Account you want this channel to drive. +3. **Verify your gateway domain** (HTML meta tag or DNS TXT). Wait until it appears in **Danh sách domain xác thực**. +4. **Set the OA Callback URL** to the same value you'll paste into GoClaw's Redirect URI field — they must match exactly. +5. **Request permissions** — open the app's Permissions tab and request: `Quản lý tin nhắn` (messages), `Quản lý người quan tâm` (followers), `Upload`, and `Webhook` (only if you'll use webhook mode). Approval can take 1–3 business days. Without these, API calls fail with `-216`. -Default DM policy is `"pairing"`. New users see pairing code instructions with 60-second debounce (no spam). Owner approves via: +> **Heads up —** Zalo's `-14003` means the domain isn't verified yet or the callback URL doesn't match what you registered. `-216` means a permission group hasn't been granted to your app. + +## GoClaw Setup Wizard + +In the GoClaw web UI go to **Channels → Add Channel → Zalo OA**. The wizard asks for three values: + +| Field | What to paste | +|-------|---------------| +| **App ID** | Numeric ID from `developers.zalo.me` | +| **Secret Key** | OAuth secret from the same console | +| **Redirect URI** | Same URL you set as the OA Callback URL | + +After you save, GoClaw opens the Zalo consent flow in a popup. Approve it with the Zalo account that owns the OA. On success, the OA ID is auto-discovered and stored encrypted; the channel detail page surfaces it read-only. + +The **Webhook Secret Key** field can stay empty during creation — you'll fill it in later if you switch to webhook mode (see [Webhook Mode](#webhook-mode)). + +> **About tokens —** Zalo issues a 1-hour access token plus a 90-day refresh token. The refresh token is **single-use** — every refresh returns a new one, and GoClaw rotates it automatically. If a refresh fails (e.g. token unused for 90 days, or revoked), the channel goes Degraded and the OA owner needs to re-run the consent flow. + +## Ingestion Modes + +The channel listens for inbound messages in exactly one of two modes per instance: + +| Mode | When to use | What runs | +|------|-------------|-----------| +| **Polling** *(default)* | No public HTTPS endpoint, simpler ops | Periodic `listrecentchat` calls on a timer | +| **Webhook** *(opt-in)* | Lower latency, event-driven, public endpoint available | Zalo POSTs to `/channels/zalo/webhook/` | + +Switching transports does not change agent behavior — both produce equivalent message shapes. Webhook deliveries do not fall back to polling on failure unless you explicitly enable `catch_up_on_restart`. + +## Webhook Mode + +Webhook mode is opt-in via `transport: "webhook"`. The setup uses a deliberate two-step bootstrap to get around Zalo's chicken-and-egg problem (you can't paste the secret before saving the URL, but Zalo verifies the URL before it shows you the secret). + +### Bootstrap flow + +1. Create the channel with `transport: "webhook"` and **leave `webhook_secret_key` empty**. +2. The gateway answers Zalo's verification ping with HTTP 200 — signature checking is skipped while the secret is empty (the channel is `Degraded — awaiting webhook secret` during this window). +3. Copy **Khóa bí mật OA** from the Zalo console and paste it into the channel's Credentials tab in GoClaw. +4. Save. The channel transitions to `Healthy` and signature verification activates. + +This same flow handles toggling an existing polling channel to webhook — just clear the secret first, save URL on Zalo, then paste the new secret. + +### Slug rules + +Each channel gets a routing slug derived from its name. Override it via `webhook_path` if you need a stable URL (e.g. `customer-support` instead of an auto-generated one). + +- Lowercase letters, digits, hyphens only +- Must start with `[a-z0-9]` +- Length 2–63 chars +- Reserved words rejected: `webhook`, `zalo`, `_health`, `_metrics` + +### Signature verification + +Zalo signs every event with `X-ZEvent-Signature: hex(SHA256(appID + body + timestamp + secret))`. The gateway verifies this against your saved secret and rejects mismatches. + +| Setting | Default | Purpose | +|---------|---------|---------| +| `webhook_signature_mode` | `"strict"` | Reject mismatches (production) | +| `webhook_signature_mode` | `"log_only"` | Warn but allow (testing) | +| `webhook_signature_mode` | `"disabled"` | Accept unsigned (diagnostic only — never in production) | +| `webhook_replay_window_seconds` | `300` (clamp `[60, 3600]`) | Reject events older than this | + +The handler also drops events where `sender.id == oa_id` — Zalo redelivers your outbound replies through the same webhook, and you don't want the agent to react to its own messages. + +## Polling Mode + +Polling is the default. The gateway calls `listrecentchat` on an interval and processes new messages. + +| Key | Default | Notes | +|-----|---------|-------| +| `poll_interval_seconds` | `15` | Range `[5, 120]` | +| `poll_count` | `10` | Page size; clamp `[1, 10]` (Zalo API hard cap — error `-210` above) | +| `poll_burndown_max_pages` | `10` | Pages allowed per cycle without sleep; clamp `[1, 20]`; set to `1` to disable burn-down | +| `catch_up_on_restart` | `false` | Single bounded sweep on Start; useful after long downtime | + +**Burn-down resilience** lets the gateway clear a backlog. A worst-case cycle of `10 × 10 = 100` messages happens before the next sleep. If 250 messages are pending, the burn-down empties them across 2–3 cycles instead of crawling 10 at a time. + +`catch_up_on_restart` is off by default because it can replay stale conversations on every restart. Turn it on if you want a single bounded `listrecentchat` sweep at boot, then normal polling resumes. + +When `transport: "webhook"`, all polling parameters are ignored. + +## Media Limits + +Zalo OA enforces per-endpoint upload caps — these are server-side, not configurable by GoClaw: + +| Endpoint | Max size | Allowed types | +|----------|----------|---------------| +| Image | 1 MB | JPEG, PNG | +| File | 5 MB | PDF, DOC, DOCX | +| GIF | 5 MB | GIF | + +Auth header for all upload calls is `access_token: ` — not a query string. GoClaw compresses oversized images before upload; oversized files fail fast with `-210`. + +## Quoted Replies + +`quote_user_message` is **on by default**. Outbound replies quote the user's last inbound message via Zalo's `message.quote_message_id` field — handy in busy CS threads where context matters. + +| Behavior | Default | Notes | +|----------|---------|-------| +| Quote first chunk of multi-chunk reply | Yes | Subsequent chunks are unquoted | +| Quote image / file / GIF sends | No | Zalo API restriction — silently dropped | +| Quote source > 48h or deleted | No | Auto-retried without quote; logs `zalo_oa.send.quote_dropped_payload_error` | + +Set `quote_user_message: false` to disable globally. + +## Status Reactions + +Zalo OA can surface agent progress as emoji reactions on the user's inbound message. Reactions don't count against Zalo's monthly active-message quota, and failures never affect channel health. + +| Level | What you see | +|-------|--------------| +| `off` *(default)* | No reactions | +| `minimal` | Terminal only — ❤ on success, 😢 on failure | +| `full` | Adds 👍 on first intermediate event (debounced ≤1 per 700ms) | + +The `minimal` level is recommended for customer-service OAs — `full` chews through the 50-reaction-per-message cap and looks unprofessional. Mid-run tool / coding / web statuses are deliberately not mapped to Zalo OA. The frontend wizard may show `minimal` as the suggested default; the runtime config default remains `off`. + +`ClearReaction` sends a `/-remove` sentinel since Zalo has no separate clear endpoint. + +## Common Errors + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| Zalo returns `-14003` during OAuth | Domain unverified or callback URL mismatch | Re-verify domain on `developers.zalo.me`; align Redirect URI exactly | +| Console URL-save fails | Gateway not reachable, or `>2s` to respond | Confirm public HTTPS reachability; channel must already exist in GoClaw | +| Channel stuck on `Degraded — awaiting webhook secret` | Operator skipped the secret-paste step | Open Credentials tab, paste **Khóa bí mật OA** | +| Webhook returns `401` | Signature mismatch (typo on paste) | Re-copy from Zalo console, save again | +| Webhook returns `404` | Channel stopped or slug mismatch | Re-enable channel; verify `webhook_path` matches Zalo console URL | +| No inbound events after secret saved | `webhook_signature_mode: "disabled"`, or Zalo auto-disabled webhook after 12h of non-200 retries | Restore mode to `strict`; re-save URL on Zalo console | +| Refresh token rejected (`-220` / `-118` / `invalid_grant`) | Refresh token unused for 90 days, revoked, or already consumed by another instance | OA owner re-runs OAuth consent; in multi-instance setups, ensure only one instance refreshes a given OA | +| API calls fail with `-216` | Permission group not approved for the app | Request the relevant group (`Quản lý tin nhắn`, `Upload`, etc.) on `developers.zalo.me` | +| `zalo_oa.webhook.bootstrap_drop` count growing | Events arriving during the secret-paste window | Normal during setup; resolves once secret is saved | + +### Zalo error code reference + +The most common Zalo codes you'll see in `zalo_oa.*.error` logs: + +| Code | Meaning | What to do | +| --- | --- | --- | +| `-14003` | Invalid redirect URI or unverified domain | Verify domain on `developers.zalo.me`; align Redirect URI exactly | +| `-201` | App not found / invalid params | Confirm App ID; inspect outbound payload shape | +| `-210` | Invalid `count` (must be ≤ 10) or upload size exceeded | Set `poll_count` ≤ 10; check media against [Media Limits](#media-limits) | +| `-216` | Insufficient permissions | Request the missing permission group on `developers.zalo.me` (1–3 day approval) | +| `-217` | Access token expired | Triggers automatic refresh — no operator action unless it persists | +| `-220` / `-118` | Refresh token expired or already used | OA owner must re-run OAuth consent flow | +| `100` | Invalid parameter | Check API call shape and field types | +| `110`–`112` | Recipient lookup failed; app not linked to OA | Re-link OA in Zalo console (`Liên kết OA` tab) | +| `210` | User not visible | User must follow the OA or grant friend permission | +| `2000`–`2004` | App rate-limited or temporarily disabled | Check app status; request quota increase | +| `12000`–`12012` | Quota / DND / friend-list / not-friend | Adjust outbound dispatch cadence | + +Full Social API table: . GoClaw's runtime classifies these codes internally as retriable, auth-refresh-triggering, or fatal — operators don't normally interact with that mapping; the symptom-based table above is the entry point for diagnostics. + +## Troubleshooting & Reference + +### Slog keys to watch ``` -/pair CODE +zalo_oa.webhook.event_received +zalo_oa.webhook.bootstrap_drop +zalo_oa.poll.burndown_capped +zalo_oa.send.quote_dropped_payload_error +zalo_webhook.handler_error +zalo_webhook.empty_message_id_streak +security.zalo_webhook_signature_mismatch ``` -## Troubleshooting +### Tracing -| Issue | Solution | -|-------|----------| -| "Invalid API key" | Check token from Zalo OA console. Ensure OA is active and Bot API enabled. | -| No messages received | Verify polling is running (check logs). Ensure OA can accept messages (not suspended). | -| Image upload fails | Verify image file exists and is under `media_max_mb`. Check file format (JPG/PNG). | -| Webhook signature mismatch | Ensure `webhook_secret` matches Zalo console. Check timestamp is recent. | -| Pairing codes not sent | Check DM policy is `"pairing"`. Verify owner can send messages to OA. | +Set `GOCLAW_ZALO_OA_TRACE=1` to dump raw Zalo response bodies at Debug level. **PII-sensitive** — never enable in production. + +### Polling config example + +```json +{ + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "polling", + "poll_interval_seconds": 15, + "poll_count": 10, + "poll_burndown_max_pages": 10, + "reaction_level": "minimal", + "quote_user_message": true, + "dm_policy": "pairing" + } + } +} +``` + +### Webhook config example + +```json +{ + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "webhook", + "webhook_path": "customer-support", + "webhook_signature_mode": "strict", + "webhook_replay_window_seconds": 300, + "reaction_level": "minimal", + "dm_policy": "pairing" + } + } +} +``` + +Credentials (`app_id`, `secret_key`, `redirect_uri`, `webhook_secret_key`) are stored encrypted via the channel's Credentials tab — they are never written to `config.json`. ## What's Next -- [Overview](/channels-overview) — Channel concepts and policies -- [Zalo Personal](/channel-zalo-personal) — Personal Zalo account integration -- [Telegram](/channel-telegram) — Telegram bot setup -- [Browser Pairing](/channel-browser-pairing) — Pairing flow +- [Channels overview](/channels-overview) — DM policies, pairing, message flow +- [Zalo Bot](/channel-zalo-bot) — static-token alternative for small deployments +- [Zalo Personal](/channel-zalo-personal) — reverse-engineered personal account diff --git a/llms.txt b/llms.txt index af89b99..96c5c61 100644 --- a/llms.txt +++ b/llms.txt @@ -62,7 +62,8 @@ - [Discord](channels/discord.md): Discord bot integration via the Discord Gateway API. Supports DMs, servers, threads, and streaming responses via message editing. - [Feishu / Lark](channels/feishu.md): **Create Feishu App:** - [Larksuite](channels/larksuite.md): **Create Larksuite App:** -- [Zalo OA](channels/zalo-oa.md): Zalo Official Account (OA) integration. DM-only with pairing-based access control and image support. +- [Zalo Bot](channels/zalo-bot.md): 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. +- [Zalo OA](channels/zalo-oa.md): Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. - [Zalo Personal](channels/zalo-personal.md): Unofficial personal Zalo account integration using reverse-engineered protocol (zcago). Supports DMs and groups with restrictive access control. - [Slack](channels/slack.md): Slack integration via Socket Mode (WebSocket). Supports DMs, channel @mentions, threaded replies, streaming, reactions, media, and message debouncing. - [WhatsApp](channels/whatsapp.md): 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). diff --git a/vi/channels/INDEX.md b/vi/channels/INDEX.md index b58c54c..1ae6524 100644 --- a/vi/channels/INDEX.md +++ b/vi/channels/INDEX.md @@ -9,25 +9,26 @@ Tài liệu đầy đủ cho tất cả các tích hợp nền tảng nhắn tin 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, chỉ DM, pairing, hình ảnh -7. **[Zalo Cá nhân](./zalo-personal.md)** — Tài khoản cá nhân (không chính thức), DM + nhóm -8. **[WhatsApp](./whatsapp.md)** — Kết nối trực tiếp, xác thực QR, media, typing indicators, pairing -9. **[WebSocket](./websocket.md)** — RPC trực tiếp, custom client, streaming events -10. **[Ghép nối trình duyệt](./browser-pairing.md)** — Xác thực mã 8 ký tự, session token +6. **[Zalo Bot](./zalo-bot.md)** — Bot token tĩnh, chỉ DM, polling/webhook +7. **[Zalo OA](./zalo-oa.md)** — Official Account (OAuth v4), polling/webhook, pairing, hình ảnh +8. **[Zalo Cá nhân](./zalo-personal.md)** — Tài khoản cá nhân (không chính thức), DM + nhóm +9. **[WhatsApp](./whatsapp.md)** — Kết nối trực tiếp, xác thực QR, media, typing indicators, pairing +10. **[WebSocket](./websocket.md)** — RPC trực tiếp, custom client, streaming events +11. **[Ghép nối trình duyệt](./browser-pairing.md)** — Xác thực mã 8 ký tự, session token ## Bảng so sánh kênh -| Tính năng | Telegram | Discord | Slack | Larksuite | Zalo OA | Zalo CN | WhatsApp | WebSocket | -|---------|----------|---------|-------|--------|---------|-----------|----------|-----------| -| **Độ phức tạp** | Dễ | Dễ | Dễ | Trung bình | Trung bình | Khó | Trung bình | Rất dễ | -| **Transport** | Polling | Gateway | Socket Mode | WS/Webhook | Polling | Protocol | Kết nối trực tiếp | WebSocket | -| **Hỗ trợ DM** | Có | Có | Có | Có | Có | Có | Có | N/A | -| **Hỗ trợ nhóm** | Có | Có | Có | Có | Không | Có | Có | N/A | -| **Streaming** | Có | Có | Có | Có | Không | Không | Không | Có | -| **Định dạng** | HTML | Markdown | mrkdwn | Cards | Plain | Plain | WA native | JSON | -| **Media** | Ảnh, Voice, File | File, Embeds | File (20MB) | Ảnh, File | Ảnh | -- | Ảnh, Video, Audio, Docs | N/A | -| **Xác thực** | Token | Token | 3 Token | App ID + Secret | API Key | Credentials | QR Code | Token + Pairing | -| **Mức rủi ro** | Thấp | Thấp | Thấp | Thấp | Thấp | Cao | Trung bình | Thấp | +| Tính năng | Telegram | Discord | Slack | Larksuite | Zalo Bot | Zalo OA | Zalo CN | WhatsApp | WebSocket | +|---------|----------|---------|-------|--------|---------|---------|-----------|----------|-----------| +| **Độ phức tạp** | Dễ | Dễ | Dễ | Trung bình | Dễ | Trung bình | Khó | Trung bình | Rất dễ | +| **Transport** | Polling | Gateway | Socket Mode | WS/Webhook | Polling/Webhook | Polling/Webhook | Protocol | Kết nối trực tiếp | WebSocket | +| **Hỗ trợ DM** | Có | Có | Có | Có | Có | Có | Có | Có | N/A | +| **Hỗ trợ nhóm** | Có | Có | Có | Có | Không | Không | Có | Có | N/A | +| **Streaming** | Có | Có | Có | Có | Không | Không | Không | Không | Có | +| **Định dạng** | HTML | Markdown | mrkdwn | Cards | Plain | Plain | Plain | WA native | JSON | +| **Media** | Ảnh, Voice, File | File, Embeds | File (20MB) | Ảnh, File | Ảnh | Ảnh | -- | Ảnh, Video, Audio, Docs | N/A | +| **Xác thực** | Token | Token | 3 Token | App ID + Secret | Bot Token | OAuth v4 | Credentials | QR Code | Token + Pairing | +| **Mức rủi ro** | Thấp | Thấp | Thấp | Thấp | Thấp | Thấp | Cao | Trung bình | Thấp | ## File cấu hình @@ -41,6 +42,7 @@ Tất cả cấu hình kênh nằm trong `config.json` gốc: "slack": { ... }, "feishu": { ... }, "zalo": { ... }, + "zalo_oa": { ... }, "zalo_personal": { ... }, "whatsapp": { ... } } diff --git a/vi/channels/zalo-bot.md b/vi/channels/zalo-bot.md new file mode 100644 index 0000000..a1a33c3 --- /dev/null +++ b/vi/channels/zalo-bot.md @@ -0,0 +1,219 @@ + + +# 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//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/` | + +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: . 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) + + diff --git a/vi/channels/zalo-oa.md b/vi/channels/zalo-oa.md index 3d7f4b2..069f013 100644 --- a/vi/channels/zalo-oa.md +++ b/vi/channels/zalo-oa.md @@ -1,120 +1,269 @@ -> Bản dịch từ [English version](/channel-zalo-oa) + -# Channel Zalo OA +# Zalo OA Channel -Tích hợp Zalo Official Account (OA). Chỉ hỗ trợ DM với kiểm soát truy cập dựa trên pairing và hỗ trợ hình ảnh. +Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. -## Thiết lập +## Overview -**Tạo Zalo OA:** +`zalo_oa` is the OAuth v4 variant of GoClaw's Zalo channel family. Operators connect a verified Zalo Official Account via the standard Zalo developer console; the gateway stores an encrypted refresh token and rotates access tokens automatically. Inbound messages reach the agent through one of two transports — long polling against `listrecentchat` (default) or webhook events POSTed by Zalo. -1. Vào https://oa.zalo.me -2. Tạo Official Account (yêu cầu số điện thoại Zalo) -3. Đặt tên OA, avatar và ảnh bìa -4. Trong cài đặt OA, vào "Settings" → "API" → "Bot API" -5. Tạo API key -6. Sao chép API key để cấu hình +This channel is meant for production deployments and supports multiple OAs per instance. -**Bật Zalo OA:** +## Choosing your Zalo variant -```json -{ - "channels": { - "zalo": { - "enabled": true, - "token": "YOUR_API_KEY", - "dm_policy": "pairing", - "allow_from": [], - "media_max_mb": 5 - } - } -} -``` +GoClaw exposes three Zalo channel types. They differ in coverage, supportability, and risk — pick by what your deployment can tolerate. -## Cấu hình +| | **Zalo OA** *(this page)* | **Zalo Bot** | **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 | -Tất cả config key nằm trong `channels.zalo`: +> **About Zalo Bot —** the platform (`bot.zapps.me`) is newer than the OA API and Zalo is actively adding endpoints (sticker and typing actions landed recently). Treat it as evolving — re-check the [Bot API docs](https://bot.zapps.me/docs/) before assuming a feature is missing. +> +> **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. -| Key | Kiểu | Mặc định | Mô tả | -|-----|------|---------|-------------| -| `enabled` | bool | false | Bật/tắt channel | -| `token` | string | bắt buộc | API key từ Zalo OA console | -| `allow_from` | list | -- | Danh sách trắng user ID | -| `dm_policy` | string | `"pairing"` | `pairing`, `allowlist`, `open`, `disabled` | -| `webhook_url` | string | -- | URL webhook tuỳ chọn (ghi đè polling) | -| `webhook_secret` | string | -- | Secret ký webhook tuỳ chọn | -| `media_max_mb` | int | 5 | Kích thước file hình ảnh tối đa (MB) | -| `block_reply` | bool | -- | Ghi đè block_reply của gateway (nil=kế thừa) | +**Quick decision** — Have an OA → **Zalo OA**. No OA but a bot is enough → [Zalo Bot](/channel-zalo-bot). Need groups, willing to accept ban risk → [Zalo Personal](/channel-zalo-personal). -## Tính năng +## Prerequisites -### Chỉ hỗ trợ DM +Two things to know first: -Zalo OA chỉ hỗ trợ nhắn tin trực tiếp. Chức năng nhóm không có sẵn. Tất cả tin nhắn được xử lý như DM. +- **App** vs **OA** — an *app* (registered at `developers.zalo.me`) holds the App ID and Secret Key. An *OA* (created at `oa.zalo.me`) is the business account users follow. One app can be linked to multiple OAs. +- **Vietnamese phone number** — required to register both the app and the OA. -### Long Polling +Then complete these on `developers.zalo.me`: -Chế độ mặc định: Bot poll Zalo API mỗi 30 giây để lấy tin nhắn mới. Server trả về tin nhắn và đánh dấu chúng đã đọc. +1. **Create / activate** the app and copy **App ID** + **Secret Key** (the OAuth secret, not the webhook secret). +2. **Link OA** — open the **Liên kết OA** tab and link the Official Account you want this channel to drive. +3. **Verify your gateway domain** (HTML meta tag or DNS TXT). Wait until it appears in **Danh sách domain xác thực**. +4. **Set the OA Callback URL** to the same value you'll paste into GoClaw's Redirect URI field — they must match exactly. +5. **Request permissions** — open the app's Permissions tab and request: `Quản lý tin nhắn` (messages), `Quản lý người quan tâm` (followers), `Upload`, and `Webhook` (only if you'll use webhook mode). Approval can take 1–3 business days. Without these, API calls fail with `-216`. -- Timeout poll: 30 giây (mặc định) -- Backoff khi lỗi: 5 giây -- Giới hạn văn bản: 2,000 ký tự mỗi tin nhắn -- Giới hạn hình ảnh: 5 MB +> **Heads up —** Zalo's `-14003` means the domain isn't verified yet or the callback URL doesn't match what you registered. `-216` means a permission group hasn't been granted to your app. -### Chế độ Webhook (Tuỳ chọn) +## GoClaw Setup Wizard -Thay vì polling, cấu hình Zalo để POST event đến gateway của bạn: +In the GoClaw web UI go to **Channels → Add Channel → Zalo OA**. The wizard asks for three values: -```json -{ - "webhook_url": "https://your-gateway.com/zalo/webhook", - "webhook_secret": "your_webhook_secret" -} -``` +| Field | What to paste | +|-------|---------------| +| **App ID** | Numeric ID from `developers.zalo.me` | +| **Secret Key** | OAuth secret from the same console | +| **Redirect URI** | Same URL you set as the OA Callback URL | + +After you save, GoClaw opens the Zalo consent flow in a popup. Approve it with the Zalo account that owns the OA. On success, the OA ID is auto-discovered and stored encrypted; the channel detail page surfaces it read-only. + +The **Webhook Secret Key** field can stay empty during creation — you'll fill it in later if you switch to webhook mode (see [Webhook Mode](#webhook-mode)). + +> **About tokens —** Zalo issues a 1-hour access token plus a 90-day refresh token. The refresh token is **single-use** — every refresh returns a new one, and GoClaw rotates it automatically. If a refresh fails (e.g. token unused for 90 days, or revoked), the channel goes Degraded and the OA owner needs to re-run the consent flow. + +## Ingestion Modes + +The channel listens for inbound messages in exactly one of two modes per instance: + +| Mode | When to use | What runs | +|------|-------------|-----------| +| **Polling** *(default)* | No public HTTPS endpoint, simpler ops | Periodic `listrecentchat` calls on a timer | +| **Webhook** *(opt-in)* | Lower latency, event-driven, public endpoint available | Zalo POSTs to `/channels/zalo/webhook/` | + +Switching transports does not change agent behavior — both produce equivalent message shapes. Webhook deliveries do not fall back to polling on failure unless you explicitly enable `catch_up_on_restart`. + +## Webhook Mode + +Webhook mode is opt-in via `transport: "webhook"`. The setup uses a deliberate two-step bootstrap to get around Zalo's chicken-and-egg problem (you can't paste the secret before saving the URL, but Zalo verifies the URL before it shows you the secret). + +### Bootstrap flow + +1. Create the channel with `transport: "webhook"` and **leave `webhook_secret_key` empty**. +2. The gateway answers Zalo's verification ping with HTTP 200 — signature checking is skipped while the secret is empty (the channel is `Degraded — awaiting webhook secret` during this window). +3. Copy **Khóa bí mật OA** from the Zalo console and paste it into the channel's Credentials tab in GoClaw. +4. Save. The channel transitions to `Healthy` and signature verification activates. + +This same flow handles toggling an existing polling channel to webhook — just clear the secret first, save URL on Zalo, then paste the new secret. + +### Slug rules + +Each channel gets a routing slug derived from its name. Override it via `webhook_path` if you need a stable URL (e.g. `customer-support` instead of an auto-generated one). + +- Lowercase letters, digits, hyphens only +- Must start with `[a-z0-9]` +- Length 2–63 chars +- Reserved words rejected: `webhook`, `zalo`, `_health`, `_metrics` + +### Signature verification + +Zalo signs every event with `X-ZEvent-Signature: hex(SHA256(appID + body + timestamp + secret))`. The gateway verifies this against your saved secret and rejects mismatches. + +| Setting | Default | Purpose | +|---------|---------|---------| +| `webhook_signature_mode` | `"strict"` | Reject mismatches (production) | +| `webhook_signature_mode` | `"log_only"` | Warn but allow (testing) | +| `webhook_signature_mode` | `"disabled"` | Accept unsigned (diagnostic only — never in production) | +| `webhook_replay_window_seconds` | `300` (clamp `[60, 3600]`) | Reject events older than this | + +The handler also drops events where `sender.id == oa_id` — Zalo redelivers your outbound replies through the same webhook, and you don't want the agent to react to its own messages. + +## Polling Mode + +Polling is the default. The gateway calls `listrecentchat` on an interval and processes new messages. + +| Key | Default | Notes | +|-----|---------|-------| +| `poll_interval_seconds` | `15` | Range `[5, 120]` | +| `poll_count` | `10` | Page size; clamp `[1, 10]` (Zalo API hard cap — error `-210` above) | +| `poll_burndown_max_pages` | `10` | Pages allowed per cycle without sleep; clamp `[1, 20]`; set to `1` to disable burn-down | +| `catch_up_on_restart` | `false` | Single bounded sweep on Start; useful after long downtime | + +**Burn-down resilience** lets the gateway clear a backlog. A worst-case cycle of `10 × 10 = 100` messages happens before the next sleep. If 250 messages are pending, the burn-down empties them across 2–3 cycles instead of crawling 10 at a time. + +`catch_up_on_restart` is off by default because it can replay stale conversations on every restart. Turn it on if you want a single bounded `listrecentchat` sweep at boot, then normal polling resumes. + +When `transport: "webhook"`, all polling parameters are ignored. -Zalo gửi chữ ký HMAC trong header `X-Zalo-Signature`. Implementation xác minh chữ ký này trước khi xử lý. +## Media Limits -### Hỗ trợ hình ảnh +Zalo OA enforces per-endpoint upload caps — these are server-side, not configurable by GoClaw: -Bot có thể nhận và gửi hình ảnh (JPG, PNG). Tối đa 5 MB mặc định. +| Endpoint | Max size | Allowed types | +|----------|----------|---------------| +| Image | 1 MB | JPEG, PNG | +| File | 5 MB | PDF, DOC, DOCX | +| GIF | 5 MB | GIF | -**Nhận**: Hình ảnh được tải xuống và lưu dưới dạng file tạm thời trong quá trình xử lý tin nhắn. +Auth header for all upload calls is `access_token: ` — not a query string. GoClaw compresses oversized images before upload; oversized files fail fast with `-210`. -**Gửi**: Hình ảnh có thể được gửi dưới dạng media attachment: +## Quoted Replies + +`quote_user_message` is **on by default**. Outbound replies quote the user's last inbound message via Zalo's `message.quote_message_id` field — handy in busy CS threads where context matters. + +| Behavior | Default | Notes | +|----------|---------|-------| +| Quote first chunk of multi-chunk reply | Yes | Subsequent chunks are unquoted | +| Quote image / file / GIF sends | No | Zalo API restriction — silently dropped | +| Quote source > 48h or deleted | No | Auto-retried without quote; logs `zalo_oa.send.quote_dropped_payload_error` | + +Set `quote_user_message: false` to disable globally. + +## Status Reactions + +Zalo OA can surface agent progress as emoji reactions on the user's inbound message. Reactions don't count against Zalo's monthly active-message quota, and failures never affect channel health. + +| Level | What you see | +|-------|--------------| +| `off` *(default)* | No reactions | +| `minimal` | Terminal only — ❤ on success, 😢 on failure | +| `full` | Adds 👍 on first intermediate event (debounced ≤1 per 700ms) | + +The `minimal` level is recommended for customer-service OAs — `full` chews through the 50-reaction-per-message cap and looks unprofessional. Mid-run tool / coding / web statuses are deliberately not mapped to Zalo OA. The frontend wizard may show `minimal` as the suggested default; the runtime config default remains `off`. + +`ClearReaction` sends a `/-remove` sentinel since Zalo has no separate clear endpoint. + +## Common Errors + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| Zalo returns `-14003` during OAuth | Domain unverified or callback URL mismatch | Re-verify domain on `developers.zalo.me`; align Redirect URI exactly | +| Console URL-save fails | Gateway not reachable, or `>2s` to respond | Confirm public HTTPS reachability; channel must already exist in GoClaw | +| Channel stuck on `Degraded — awaiting webhook secret` | Operator skipped the secret-paste step | Open Credentials tab, paste **Khóa bí mật OA** | +| Webhook returns `401` | Signature mismatch (typo on paste) | Re-copy from Zalo console, save again | +| Webhook returns `404` | Channel stopped or slug mismatch | Re-enable channel; verify `webhook_path` matches Zalo console URL | +| No inbound events after secret saved | `webhook_signature_mode: "disabled"`, or Zalo auto-disabled webhook after 12h of non-200 retries | Restore mode to `strict`; re-save URL on Zalo console | +| Refresh token rejected (`-220` / `-118` / `invalid_grant`) | Refresh token unused for 90 days, revoked, or already consumed by another instance | OA owner re-runs OAuth consent; in multi-instance setups, ensure only one instance refreshes a given OA | +| API calls fail with `-216` | Permission group not approved for the app | Request the relevant group (`Quản lý tin nhắn`, `Upload`, etc.) on `developers.zalo.me` | +| `zalo_oa.webhook.bootstrap_drop` count growing | Events arriving during the secret-paste window | Normal during setup; resolves once secret is saved | + +### Zalo error code reference + +The most common Zalo codes you'll see in `zalo_oa.*.error` logs: + +| Code | Meaning | What to do | +| --- | --- | --- | +| `-14003` | Invalid redirect URI or unverified domain | Verify domain on `developers.zalo.me`; align Redirect URI exactly | +| `-201` | App not found / invalid params | Confirm App ID; inspect outbound payload shape | +| `-210` | Invalid `count` (must be ≤ 10) or upload size exceeded | Set `poll_count` ≤ 10; check media against [Media Limits](#media-limits) | +| `-216` | Insufficient permissions | Request the missing permission group on `developers.zalo.me` (1–3 day approval) | +| `-217` | Access token expired | Triggers automatic refresh — no operator action unless it persists | +| `-220` / `-118` | Refresh token expired or already used | OA owner must re-run OAuth consent flow | +| `100` | Invalid parameter | Check API call shape and field types | +| `110`–`112` | Recipient lookup failed; app not linked to OA | Re-link OA in Zalo console (`Liên kết OA` tab) | +| `210` | User not visible | User must follow the OA or grant friend permission | +| `2000`–`2004` | App rate-limited or temporarily disabled | Check app status; request quota increase | +| `12000`–`12012` | Quota / DND / friend-list / not-friend | Adjust outbound dispatch cadence | + +Full Social API table: . GoClaw's runtime classifies these codes internally as retriable, auth-refresh-triggering, or fatal — operators don't normally interact with that mapping; the symptom-based table above is the entry point for diagnostics. + +## Troubleshooting & Reference + +### Slog keys to watch + +``` +zalo_oa.webhook.event_received +zalo_oa.webhook.bootstrap_drop +zalo_oa.poll.burndown_capped +zalo_oa.send.quote_dropped_payload_error +zalo_webhook.handler_error +zalo_webhook.empty_message_id_streak +security.zalo_webhook_signature_mismatch +``` + +### Tracing + +Set `GOCLAW_ZALO_OA_TRACE=1` to dump raw Zalo response bodies at Debug level. **PII-sensitive** — never enable in production. + +### Polling config example ```json { - "channel": "zalo", - "content": "Here's your image", - "media": [ - { "url": "/tmp/image.jpg", "type": "image" } - ] + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "polling", + "poll_interval_seconds": 15, + "poll_count": 10, + "poll_burndown_max_pages": 10, + "reaction_level": "minimal", + "quote_user_message": true, + "dm_policy": "pairing" + } + } } ``` -### Pairing mặc định - -Chính sách DM mặc định là `"pairing"`. User mới thấy hướng dẫn mã pairing với debounce 60 giây (không spam). Chủ sở hữu phê duyệt qua: +### Webhook config example +```json +{ + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "webhook", + "webhook_path": "customer-support", + "webhook_signature_mode": "strict", + "webhook_replay_window_seconds": 300, + "reaction_level": "minimal", + "dm_policy": "pairing" + } + } +} ``` -/pair CODE -``` - -## Xử lý sự cố -| Vấn đề | Giải pháp | -|-------|----------| -| "Invalid API key" | Kiểm tra token từ Zalo OA console. Đảm bảo OA đang hoạt động và Bot API đã được bật. | -| Không nhận được tin nhắn | Xác minh polling đang chạy (kiểm tra log). Đảm bảo OA có thể nhận tin nhắn (không bị tạm ngưng). | -| Upload hình ảnh thất bại | Xác minh file hình ảnh tồn tại và dưới `media_max_mb`. Kiểm tra định dạng file (JPG/PNG). | -| Chữ ký webhook không khớp | Đảm bảo `webhook_secret` khớp với Zalo console. Kiểm tra timestamp có còn gần đây không. | -| Mã pairing không được gửi | Kiểm tra chính sách DM là `"pairing"`. Xác minh chủ sở hữu có thể gửi tin nhắn đến OA. | +Credentials (`app_id`, `secret_key`, `redirect_uri`, `webhook_secret_key`) are stored encrypted via the channel's Credentials tab — they are never written to `config.json`. -## Tiếp theo +## What's Next -- [Tổng quan](/channels-overview) — Khái niệm và chính sách channel -- [Zalo Personal](/channel-zalo-personal) — Tích hợp tài khoản Zalo cá nhân -- [Telegram](/channel-telegram) — Thiết lập Telegram bot -- [Browser Pairing](/channel-browser-pairing) — Luồng pairing +- [Channels overview](/channels-overview) — DM policies, pairing, message flow +- [Zalo Bot](/channel-zalo-bot) — static-token alternative for small deployments +- [Zalo Personal](/channel-zalo-personal) — reverse-engineered personal account - + diff --git a/vi/llms-full.txt b/vi/llms-full.txt index 7695faa..f9fd7ac 100644 --- a/vi/llms-full.txt +++ b/vi/llms-full.txt @@ -9238,124 +9238,9 @@ Tiền tố `group:fs` chọn tất cả tool trong nhóm `fs` (Feishu/Lark). C --- -> Bản dịch từ [English version](/channel-zalo-oa) -# Channel Zalo OA -Tích hợp Zalo Official Account (OA). Chỉ hỗ trợ DM với kiểm soát truy cập dựa trên pairing và hỗ trợ hình ảnh. - -## Thiết lập - -**Tạo Zalo OA:** - -1. Vào https://oa.zalo.me -2. Tạo Official Account (yêu cầu số điện thoại Zalo) -3. Đặt tên OA, avatar và ảnh bìa -4. Trong cài đặt OA, vào "Settings" → "API" → "Bot API" -5. Tạo API key -6. Sao chép API key để cấu hình - -**Bật Zalo OA:** - -```json -{ - "channels": { - "zalo": { - "enabled": true, - "token": "YOUR_API_KEY", - "dm_policy": "pairing", - "allow_from": [], - "media_max_mb": 5 - } - } -} -``` - -## Cấu hình - -Tất cả config key nằm trong `channels.zalo`: - -| Key | Kiểu | Mặc định | Mô tả | -|-----|------|---------|-------------| -| `enabled` | bool | false | Bật/tắt channel | -| `token` | string | bắt buộc | API key từ Zalo OA console | -| `allow_from` | list | -- | Danh sách trắng user ID | -| `dm_policy` | string | `"pairing"` | `pairing`, `allowlist`, `open`, `disabled` | -| `webhook_url` | string | -- | URL webhook tuỳ chọn (ghi đè polling) | -| `webhook_secret` | string | -- | Secret ký webhook tuỳ chọn | -| `media_max_mb` | int | 5 | Kích thước file hình ảnh tối đa (MB) | -| `block_reply` | bool | -- | Ghi đè block_reply của gateway (nil=kế thừa) | - -## Tính năng - -### Chỉ hỗ trợ DM - -Zalo OA chỉ hỗ trợ nhắn tin trực tiếp. Chức năng nhóm không có sẵn. Tất cả tin nhắn được xử lý như DM. - -### Long Polling - -Chế độ mặc định: Bot poll Zalo API mỗi 30 giây để lấy tin nhắn mới. Server trả về tin nhắn và đánh dấu chúng đã đọc. - -- Timeout poll: 30 giây (mặc định) -- Backoff khi lỗi: 5 giây -- Giới hạn văn bản: 2,000 ký tự mỗi tin nhắn -- Giới hạn hình ảnh: 5 MB - -### Chế độ Webhook (Tuỳ chọn) - -Thay vì polling, cấu hình Zalo để POST event đến gateway của bạn: - -```json -{ - "webhook_url": "https://your-gateway.com/zalo/webhook", - "webhook_secret": "your_webhook_secret" -} -``` - -Zalo gửi chữ ký HMAC trong header `X-Zalo-Signature`. Implementation xác minh chữ ký này trước khi xử lý. - -### Hỗ trợ hình ảnh - -Bot có thể nhận và gửi hình ảnh (JPG, PNG). Tối đa 5 MB mặc định. - -**Nhận**: Hình ảnh được tải xuống và lưu dưới dạng file tạm thời trong quá trình xử lý tin nhắn. - -**Gửi**: Hình ảnh có thể được gửi dưới dạng media attachment: - -```json -{ - "channel": "zalo", - "content": "Here's your image", - "media": [ - { "url": "/tmp/image.jpg", "type": "image" } - ] -} -``` - -### Pairing mặc định - -Chính sách DM mặc định là `"pairing"`. User mới thấy hướng dẫn mã pairing với debounce 60 giây (không spam). Chủ sở hữu phê duyệt qua: - -``` -/pair CODE -``` - -## Xử lý sự cố - -| Vấn đề | Giải pháp | -|-------|----------| -| "Invalid API key" | Kiểm tra token từ Zalo OA console. Đảm bảo OA đang hoạt động và Bot API đã được bật. | -| Không nhận được tin nhắn | Xác minh polling đang chạy (kiểm tra log). Đảm bảo OA có thể nhận tin nhắn (không bị tạm ngưng). | -| Upload hình ảnh thất bại | Xác minh file hình ảnh tồn tại và dưới `media_max_mb`. Kiểm tra định dạng file (JPG/PNG). | -| Chữ ký webhook không khớp | Đảm bảo `webhook_secret` khớp với Zalo console. Kiểm tra timestamp có còn gần đây không. | -| Mã pairing không được gửi | Kiểm tra chính sách DM là `"pairing"`. Xác minh chủ sở hữu có thể gửi tin nhắn đến OA. | - -## Tiếp theo - -- [Tổng quan](/channels-overview) — Khái niệm và chính sách channel -- [Zalo Personal](/channel-zalo-personal) — Tích hợp tài khoản Zalo cá nhân -- [Telegram](/channel-telegram) — Thiết lập Telegram bot -- [Browser Pairing](/channel-browser-pairing) — Luồng pairing +--- diff --git a/vi/llms.txt b/vi/llms.txt index b6fa5a8..9785cd0 100644 --- a/vi/llms.txt +++ b/vi/llms.txt @@ -62,7 +62,8 @@ - [Discord](vi/channels/discord.md): Tích hợp Discord bot qua Discord Gateway API. Hỗ trợ DM, server, thread, và phản hồi streaming qua chỉnh sửa tin nhắn. - [Feishu / Lark](vi/channels/feishu.md): Tích hợp nhắn tin [Feishu](https://www.feishu.cn/) (飞书) dành cho người dùng tại Trung Quốc — hỗ trợ DM, nhóm, streaming card và cập nhật thời gian thực qua WebSocket hoặc webhook. - [Larksuite](vi/channels/larksuite.md): Tích hợp nhắn tin [Larksuite](https://www.larksuite.com/) hỗ trợ DM, nhóm, streaming card, và cập nhật theo thời gian thực qua WebSocket hoặc webhook. -- [Zalo OA](vi/channels/zalo-oa.md): Tích hợp Zalo Official Account (OA). Chỉ hỗ trợ DM với kiểm soát truy cập dựa trên pairing và hỗ trợ hình ảnh. +- [Zalo Bot](vi/channels/zalo-bot.md): 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. +- [Zalo OA](vi/channels/zalo-oa.md): Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. - [Zalo Personal](vi/channels/zalo-personal.md): Tích hợp tài khoản Zalo cá nhân không chính thức sử dụng giao thức được dịch ngược (zcago). Hỗ trợ DM và nhóm với kiểm soát truy cập hạn chế. - [Slack](vi/channels/slack.md): Tích hợp Slack qua Socket Mode (WebSocket). Hỗ trợ DM, @mention trong channel, trả lời theo thread, streaming, reaction, media, và message debouncing. - [WhatsApp](vi/channels/whatsapp.md): Tích hợp WhatsApp trực tiếp. GoClaw kết nối trực tiếp đến giao thức multi-device của WhatsApp — không cần bridge hay dịch vụ Node.js bên ngoài. Trạng thái xác thực được lưu trong database (PostgreSQL hoặc SQLite). diff --git a/zh/channels/INDEX.md b/zh/channels/INDEX.md index d27ec6f..fbcf996 100644 --- a/zh/channels/INDEX.md +++ b/zh/channels/INDEX.md @@ -11,26 +11,27 @@ GoClaw 所有消息平台集成的完整文档。 3. **[Discord](./discord.md)** — Gateway API、占位符编辑、线程 4. **[Slack](./slack.md)** — Socket Mode、线程、流式输出、表情回应、防抖 5. **[Larksuite](./larksuite.md)** — WebSocket/Webhook、流式卡片、媒体 -6. **[Zalo OA](./zalo-oa.md)** — 官方账号、仅 DM、配对、图片 -7. **[Zalo 个人](./zalo-personal.md)** — 个人账号(非官方)、DM + 群组 -8. **[WhatsApp](./whatsapp.md)** — 直连、QR 认证、媒体、输入指示器、配对 -9. **[WebSocket](./websocket.md)** — 直接 RPC、自定义客户端、流式事件 -10. **[Browser Pairing](./browser-pairing.md)** — 8 位码认证、session token +6. **[Zalo Bot](./zalo-bot.md)** — 静态 token bot、仅 DM、轮询/webhook +7. **[Zalo OA](./zalo-oa.md)** — 官方账号(OAuth v4)、轮询/webhook、配对、图片 +8. **[Zalo 个人](./zalo-personal.md)** — 个人账号(非官方)、DM + 群组 +9. **[WhatsApp](./whatsapp.md)** — 直连、QR 认证、媒体、输入指示器、配对 +10. **[WebSocket](./websocket.md)** — 直接 RPC、自定义客户端、流式事件 +11. **[Browser Pairing](./browser-pairing.md)** — 8 位码认证、session token ## Channel 对比表 -| 功能 | Telegram | Discord | Slack | Larksuite | Zalo OA | Zalo 个人 | WhatsApp | WebSocket | -|---------|----------|---------|-------|--------|---------|-----------|----------|-----------| -| **设置复杂度** | 简单 | 简单 | 简单 | 中等 | 中等 | 困难 | 中等 | 非常简单 | -| **传输方式** | 轮询 | Gateway | Socket Mode | WS/Webhook | 轮询 | 协议 | 直连 | WebSocket | -| **DM 支持** | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 无 | -| **群组支持** | 是 | 是 | 是 | 是 | 否 | 是 | 是 | 无 | -| **流式输出** | 是 | 是 | 是 | 是 | 否 | 否 | 否 | 是 | -| **富文本格式** | HTML | Markdown | mrkdwn | 卡片 | 纯文本 | 纯文本 | WA 原生 | JSON | -| **表情回应** | 是 | -- | 是 | 是 | -- | -- | -- | -- | -| **媒体** | 图片、语音、文件 | 文件、嵌入 | 文件(20MB) | 图片、文件 | 图片 | -- | 图片、视频、音频、文档 | 无 | -| **认证方式** | Token | Token | 3 Tokens | App ID + Secret | API Key | 凭据 | QR 码 | Token + 配对 | -| **风险等级** | 低 | 低 | 低 | 低 | 低 | 高 | 中 | 低 | +| 功能 | Telegram | Discord | Slack | Larksuite | Zalo Bot | Zalo OA | Zalo 个人 | WhatsApp | WebSocket | +|---------|----------|---------|-------|--------|---------|---------|-----------|----------|-----------| +| **设置复杂度** | 简单 | 简单 | 简单 | 中等 | 简单 | 中等 | 困难 | 中等 | 非常简单 | +| **传输方式** | 轮询 | Gateway | Socket Mode | WS/Webhook | 轮询/Webhook | 轮询/Webhook | 协议 | 直连 | WebSocket | +| **DM 支持** | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 是 | 无 | +| **群组支持** | 是 | 是 | 是 | 是 | 否 | 否 | 是 | 是 | 无 | +| **流式输出** | 是 | 是 | 是 | 是 | 否 | 否 | 否 | 否 | 是 | +| **富文本格式** | HTML | Markdown | mrkdwn | 卡片 | 纯文本 | 纯文本 | 纯文本 | WA 原生 | JSON | +| **表情回应** | 是 | -- | 是 | 是 | -- | 是 | -- | -- | -- | +| **媒体** | 图片、语音、文件 | 文件、嵌入 | 文件(20MB) | 图片、文件 | 图片 | 图片 | -- | 图片、视频、音频、文档 | 无 | +| **认证方式** | Token | Token | 3 Tokens | App ID + Secret | Bot Token | OAuth v4 | 凭据 | QR 码 | Token + 配对 | +| **风险等级** | 低 | 低 | 低 | 低 | 低 | 低 | 高 | 中 | 低 | ## 配置文件 @@ -44,6 +45,7 @@ GoClaw 所有消息平台集成的完整文档。 "slack": { ... }, "feishu": { ... }, "zalo": { ... }, + "zalo_oa": { ... }, "zalo_personal": { ... }, "whatsapp": { ... } } @@ -128,12 +130,20 @@ GoClaw 所有消息平台集成的完整文档。 - [ ] 若使用 webhook:在 Larksuite 控制台设置 URL - [ ] 在配置中启用 +### Zalo Bot + +- [ ] 在 developers.zalo.me Bot API 创建/打开 bot +- [ ] 复制访问 token +- [ ] 在配置中启用(`channels.zalo`),默认轮询 +- [ ] 可选:通过 `transport: "webhook"` 切换为 webhook 并设置 secret + ### Zalo OA -- [ ] 在 oa.zalo.me 创建官方账号 -- [ ] 启用 Bot API -- [ ] 复制 API key -- [ ] 在配置中启用(默认轮询) +- [ ] 在 developers.zalo.me 验证网关域名 +- [ ] 在 Zalo 控制台设置 OA Callback URL +- [ ] 复制 App ID + Secret Key +- [ ] 在 GoClaw 中运行 OAuth 向导(App ID、Secret、Redirect URI) +- [ ] 在配置中启用(默认轮询;如需 webhook 走 bootstrap 流程) ### Zalo 个人 diff --git a/zh/channels/zalo-bot.md b/zh/channels/zalo-bot.md new file mode 100644 index 0000000..6ea19a8 --- /dev/null +++ b/zh/channels/zalo-bot.md @@ -0,0 +1,219 @@ + + +# 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//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/` | + +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: . 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) + + diff --git a/zh/channels/zalo-oa.md b/zh/channels/zalo-oa.md index 017562b..6801e4a 100644 --- a/zh/channels/zalo-oa.md +++ b/zh/channels/zalo-oa.md @@ -1,120 +1,269 @@ -> 翻译自 [English version](/channel-zalo-oa) + # Zalo OA Channel -Zalo 官方账号(OA)集成。仅支持 DM,基于配对的访问控制,支持图片。 +Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. -## 设置 +## Overview -**创建 Zalo OA:** +`zalo_oa` is the OAuth v4 variant of GoClaw's Zalo channel family. Operators connect a verified Zalo Official Account via the standard Zalo developer console; the gateway stores an encrypted refresh token and rotates access tokens automatically. Inbound messages reach the agent through one of two transports — long polling against `listrecentchat` (default) or webhook events POSTed by Zalo. -1. 前往 https://oa.zalo.me -2. 创建官方账号(需要 Zalo 手机号) -3. 设置 OA 名称、头像和封面照片 -4. 在 OA 设置中,进入"Settings" → "API" → "Bot API" -5. 创建 API key -6. 复制 API key 用于配置 +This channel is meant for production deployments and supports multiple OAs per instance. -**启用 Zalo OA:** +## Choosing your Zalo variant -```json -{ - "channels": { - "zalo": { - "enabled": true, - "token": "YOUR_API_KEY", - "dm_policy": "pairing", - "allow_from": [], - "media_max_mb": 5 - } - } -} -``` +GoClaw exposes three Zalo channel types. They differ in coverage, supportability, and risk — pick by what your deployment can tolerate. -## 配置 +| | **Zalo OA** *(this page)* | **Zalo Bot** | **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 | -所有配置项位于 `channels.zalo`: +> **About Zalo Bot —** the platform (`bot.zapps.me`) is newer than the OA API and Zalo is actively adding endpoints (sticker and typing actions landed recently). Treat it as evolving — re-check the [Bot API docs](https://bot.zapps.me/docs/) before assuming a feature is missing. +> +> **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. -| 配置项 | 类型 | 默认值 | 说明 | -|-----|------|---------|-------------| -| `enabled` | bool | false | 启用/禁用 channel | -| `token` | string | 必填 | 来自 Zalo OA 控制台的 API key | -| `allow_from` | list | -- | 用户 ID 白名单 | -| `dm_policy` | string | `"pairing"` | `pairing`、`allowlist`、`open`、`disabled` | -| `webhook_url` | string | -- | 可选 webhook URL(覆盖轮询) | -| `webhook_secret` | string | -- | 可选 webhook 签名密钥 | -| `media_max_mb` | int | 5 | 最大图片文件大小(MB) | -| `block_reply` | bool | -- | 覆盖 gateway block_reply(nil=继承) | +**Quick decision** — Have an OA → **Zalo OA**. No OA but a bot is enough → [Zalo Bot](/channel-zalo-bot). Need groups, willing to accept ban risk → [Zalo Personal](/channel-zalo-personal). -## 功能特性 +## Prerequisites -### 仅限 DM +Two things to know first: -Zalo OA 只支持直接消息。群组功能不可用。所有消息均视为 DM。 +- **App** vs **OA** — an *app* (registered at `developers.zalo.me`) holds the App ID and Secret Key. An *OA* (created at `oa.zalo.me`) is the business account users follow. One app can be linked to multiple OAs. +- **Vietnamese phone number** — required to register both the app and the OA. -### 长轮询 +Then complete these on `developers.zalo.me`: -默认模式:Bot 每 30 秒轮询 Zalo API 获取新消息。服务器返回消息并标记为已读。 +1. **Create / activate** the app and copy **App ID** + **Secret Key** (the OAuth secret, not the webhook secret). +2. **Link OA** — open the **Liên kết OA** tab and link the Official Account you want this channel to drive. +3. **Verify your gateway domain** (HTML meta tag or DNS TXT). Wait until it appears in **Danh sách domain xác thực**. +4. **Set the OA Callback URL** to the same value you'll paste into GoClaw's Redirect URI field — they must match exactly. +5. **Request permissions** — open the app's Permissions tab and request: `Quản lý tin nhắn` (messages), `Quản lý người quan tâm` (followers), `Upload`, and `Webhook` (only if you'll use webhook mode). Approval can take 1–3 business days. Without these, API calls fail with `-216`. -- 轮询超时:30 秒(默认) -- 错误退避:5 秒 -- 文本限制:每条消息 2,000 字符 -- 图片限制:5 MB +> **Heads up —** Zalo's `-14003` means the domain isn't verified yet or the callback URL doesn't match what you registered. `-216` means a permission group hasn't been granted to your app. -### Webhook 模式(可选) +## GoClaw Setup Wizard -不使用轮询,改为配置 Zalo 将事件 POST 到你的 gateway: +In the GoClaw web UI go to **Channels → Add Channel → Zalo OA**. The wizard asks for three values: -```json -{ - "webhook_url": "https://your-gateway.com/zalo/webhook", - "webhook_secret": "your_webhook_secret" -} -``` +| Field | What to paste | +|-------|---------------| +| **App ID** | Numeric ID from `developers.zalo.me` | +| **Secret Key** | OAuth secret from the same console | +| **Redirect URI** | Same URL you set as the OA Callback URL | + +After you save, GoClaw opens the Zalo consent flow in a popup. Approve it with the Zalo account that owns the OA. On success, the OA ID is auto-discovered and stored encrypted; the channel detail page surfaces it read-only. + +The **Webhook Secret Key** field can stay empty during creation — you'll fill it in later if you switch to webhook mode (see [Webhook Mode](#webhook-mode)). + +> **About tokens —** Zalo issues a 1-hour access token plus a 90-day refresh token. The refresh token is **single-use** — every refresh returns a new one, and GoClaw rotates it automatically. If a refresh fails (e.g. token unused for 90 days, or revoked), the channel goes Degraded and the OA owner needs to re-run the consent flow. + +## Ingestion Modes + +The channel listens for inbound messages in exactly one of two modes per instance: + +| Mode | When to use | What runs | +|------|-------------|-----------| +| **Polling** *(default)* | No public HTTPS endpoint, simpler ops | Periodic `listrecentchat` calls on a timer | +| **Webhook** *(opt-in)* | Lower latency, event-driven, public endpoint available | Zalo POSTs to `/channels/zalo/webhook/` | + +Switching transports does not change agent behavior — both produce equivalent message shapes. Webhook deliveries do not fall back to polling on failure unless you explicitly enable `catch_up_on_restart`. + +## Webhook Mode + +Webhook mode is opt-in via `transport: "webhook"`. The setup uses a deliberate two-step bootstrap to get around Zalo's chicken-and-egg problem (you can't paste the secret before saving the URL, but Zalo verifies the URL before it shows you the secret). + +### Bootstrap flow + +1. Create the channel with `transport: "webhook"` and **leave `webhook_secret_key` empty**. +2. The gateway answers Zalo's verification ping with HTTP 200 — signature checking is skipped while the secret is empty (the channel is `Degraded — awaiting webhook secret` during this window). +3. Copy **Khóa bí mật OA** from the Zalo console and paste it into the channel's Credentials tab in GoClaw. +4. Save. The channel transitions to `Healthy` and signature verification activates. + +This same flow handles toggling an existing polling channel to webhook — just clear the secret first, save URL on Zalo, then paste the new secret. + +### Slug rules + +Each channel gets a routing slug derived from its name. Override it via `webhook_path` if you need a stable URL (e.g. `customer-support` instead of an auto-generated one). + +- Lowercase letters, digits, hyphens only +- Must start with `[a-z0-9]` +- Length 2–63 chars +- Reserved words rejected: `webhook`, `zalo`, `_health`, `_metrics` + +### Signature verification + +Zalo signs every event with `X-ZEvent-Signature: hex(SHA256(appID + body + timestamp + secret))`. The gateway verifies this against your saved secret and rejects mismatches. + +| Setting | Default | Purpose | +|---------|---------|---------| +| `webhook_signature_mode` | `"strict"` | Reject mismatches (production) | +| `webhook_signature_mode` | `"log_only"` | Warn but allow (testing) | +| `webhook_signature_mode` | `"disabled"` | Accept unsigned (diagnostic only — never in production) | +| `webhook_replay_window_seconds` | `300` (clamp `[60, 3600]`) | Reject events older than this | + +The handler also drops events where `sender.id == oa_id` — Zalo redelivers your outbound replies through the same webhook, and you don't want the agent to react to its own messages. + +## Polling Mode + +Polling is the default. The gateway calls `listrecentchat` on an interval and processes new messages. + +| Key | Default | Notes | +|-----|---------|-------| +| `poll_interval_seconds` | `15` | Range `[5, 120]` | +| `poll_count` | `10` | Page size; clamp `[1, 10]` (Zalo API hard cap — error `-210` above) | +| `poll_burndown_max_pages` | `10` | Pages allowed per cycle without sleep; clamp `[1, 20]`; set to `1` to disable burn-down | +| `catch_up_on_restart` | `false` | Single bounded sweep on Start; useful after long downtime | + +**Burn-down resilience** lets the gateway clear a backlog. A worst-case cycle of `10 × 10 = 100` messages happens before the next sleep. If 250 messages are pending, the burn-down empties them across 2–3 cycles instead of crawling 10 at a time. + +`catch_up_on_restart` is off by default because it can replay stale conversations on every restart. Turn it on if you want a single bounded `listrecentchat` sweep at boot, then normal polling resumes. + +When `transport: "webhook"`, all polling parameters are ignored. -Zalo 在请求头 `X-Zalo-Signature` 中发送 HMAC 签名。处理前先验证签名。 +## Media Limits -### 图片支持 +Zalo OA enforces per-endpoint upload caps — these are server-side, not configurable by GoClaw: -Bot 可以接收和发送图片(JPG、PNG)。默认最大 5 MB。 +| Endpoint | Max size | Allowed types | +|----------|----------|---------------| +| Image | 1 MB | JPEG, PNG | +| File | 5 MB | PDF, DOC, DOCX | +| GIF | 5 MB | GIF | -**接收**:图片在消息处理期间下载并以临时文件保存。 +Auth header for all upload calls is `access_token: ` — not a query string. GoClaw compresses oversized images before upload; oversized files fail fast with `-210`. -**发送**:图片作为媒体附件发送: +## Quoted Replies + +`quote_user_message` is **on by default**. Outbound replies quote the user's last inbound message via Zalo's `message.quote_message_id` field — handy in busy CS threads where context matters. + +| Behavior | Default | Notes | +|----------|---------|-------| +| Quote first chunk of multi-chunk reply | Yes | Subsequent chunks are unquoted | +| Quote image / file / GIF sends | No | Zalo API restriction — silently dropped | +| Quote source > 48h or deleted | No | Auto-retried without quote; logs `zalo_oa.send.quote_dropped_payload_error` | + +Set `quote_user_message: false` to disable globally. + +## Status Reactions + +Zalo OA can surface agent progress as emoji reactions on the user's inbound message. Reactions don't count against Zalo's monthly active-message quota, and failures never affect channel health. + +| Level | What you see | +|-------|--------------| +| `off` *(default)* | No reactions | +| `minimal` | Terminal only — ❤ on success, 😢 on failure | +| `full` | Adds 👍 on first intermediate event (debounced ≤1 per 700ms) | + +The `minimal` level is recommended for customer-service OAs — `full` chews through the 50-reaction-per-message cap and looks unprofessional. Mid-run tool / coding / web statuses are deliberately not mapped to Zalo OA. The frontend wizard may show `minimal` as the suggested default; the runtime config default remains `off`. + +`ClearReaction` sends a `/-remove` sentinel since Zalo has no separate clear endpoint. + +## Common Errors + +| Symptom | Likely cause | Fix | +|---------|--------------|-----| +| Zalo returns `-14003` during OAuth | Domain unverified or callback URL mismatch | Re-verify domain on `developers.zalo.me`; align Redirect URI exactly | +| Console URL-save fails | Gateway not reachable, or `>2s` to respond | Confirm public HTTPS reachability; channel must already exist in GoClaw | +| Channel stuck on `Degraded — awaiting webhook secret` | Operator skipped the secret-paste step | Open Credentials tab, paste **Khóa bí mật OA** | +| Webhook returns `401` | Signature mismatch (typo on paste) | Re-copy from Zalo console, save again | +| Webhook returns `404` | Channel stopped or slug mismatch | Re-enable channel; verify `webhook_path` matches Zalo console URL | +| No inbound events after secret saved | `webhook_signature_mode: "disabled"`, or Zalo auto-disabled webhook after 12h of non-200 retries | Restore mode to `strict`; re-save URL on Zalo console | +| Refresh token rejected (`-220` / `-118` / `invalid_grant`) | Refresh token unused for 90 days, revoked, or already consumed by another instance | OA owner re-runs OAuth consent; in multi-instance setups, ensure only one instance refreshes a given OA | +| API calls fail with `-216` | Permission group not approved for the app | Request the relevant group (`Quản lý tin nhắn`, `Upload`, etc.) on `developers.zalo.me` | +| `zalo_oa.webhook.bootstrap_drop` count growing | Events arriving during the secret-paste window | Normal during setup; resolves once secret is saved | + +### Zalo error code reference + +The most common Zalo codes you'll see in `zalo_oa.*.error` logs: + +| Code | Meaning | What to do | +| --- | --- | --- | +| `-14003` | Invalid redirect URI or unverified domain | Verify domain on `developers.zalo.me`; align Redirect URI exactly | +| `-201` | App not found / invalid params | Confirm App ID; inspect outbound payload shape | +| `-210` | Invalid `count` (must be ≤ 10) or upload size exceeded | Set `poll_count` ≤ 10; check media against [Media Limits](#media-limits) | +| `-216` | Insufficient permissions | Request the missing permission group on `developers.zalo.me` (1–3 day approval) | +| `-217` | Access token expired | Triggers automatic refresh — no operator action unless it persists | +| `-220` / `-118` | Refresh token expired or already used | OA owner must re-run OAuth consent flow | +| `100` | Invalid parameter | Check API call shape and field types | +| `110`–`112` | Recipient lookup failed; app not linked to OA | Re-link OA in Zalo console (`Liên kết OA` tab) | +| `210` | User not visible | User must follow the OA or grant friend permission | +| `2000`–`2004` | App rate-limited or temporarily disabled | Check app status; request quota increase | +| `12000`–`12012` | Quota / DND / friend-list / not-friend | Adjust outbound dispatch cadence | + +Full Social API table: . GoClaw's runtime classifies these codes internally as retriable, auth-refresh-triggering, or fatal — operators don't normally interact with that mapping; the symptom-based table above is the entry point for diagnostics. + +## Troubleshooting & Reference + +### Slog keys to watch + +``` +zalo_oa.webhook.event_received +zalo_oa.webhook.bootstrap_drop +zalo_oa.poll.burndown_capped +zalo_oa.send.quote_dropped_payload_error +zalo_webhook.handler_error +zalo_webhook.empty_message_id_streak +security.zalo_webhook_signature_mismatch +``` + +### Tracing + +Set `GOCLAW_ZALO_OA_TRACE=1` to dump raw Zalo response bodies at Debug level. **PII-sensitive** — never enable in production. + +### Polling config example ```json { - "channel": "zalo", - "content": "Here's your image", - "media": [ - { "url": "/tmp/image.jpg", "type": "image" } - ] + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "polling", + "poll_interval_seconds": 15, + "poll_count": 10, + "poll_burndown_max_pages": 10, + "reaction_level": "minimal", + "quote_user_message": true, + "dm_policy": "pairing" + } + } } ``` -### 默认配对 - -默认 DM 策略为 `"pairing"`。新用户看到配对码说明,带 60 秒防抖(不会被刷屏)。管理员通过以下方式审批: +### Webhook config example +```json +{ + "channels": { + "zalo_oa": { + "enabled": true, + "transport": "webhook", + "webhook_path": "customer-support", + "webhook_signature_mode": "strict", + "webhook_replay_window_seconds": 300, + "reaction_level": "minimal", + "dm_policy": "pairing" + } + } +} ``` -/pair CODE -``` - -## 故障排查 -| 问题 | 解决方案 | -|-------|----------| -| "Invalid API key" | 检查来自 Zalo OA 控制台的 token。确保 OA 处于活跃状态且 Bot API 已启用。 | -| 未收到消息 | 验证轮询是否运行中(检查日志)。确保 OA 可以接收消息(未被暂停)。 | -| 图片上传失败 | 验证图片文件存在且在 `media_max_mb` 以内。检查文件格式(JPG/PNG)。 | -| Webhook 签名不匹配 | 确保 `webhook_secret` 与 Zalo 控制台一致。检查时间戳是否最新。 | -| 配对码未发送 | 检查 DM 策略是否为 `"pairing"`。验证管理员可以向 OA 发送消息。 | +Credentials (`app_id`, `secret_key`, `redirect_uri`, `webhook_secret_key`) are stored encrypted via the channel's Credentials tab — they are never written to `config.json`. -## 下一步 +## What's Next -- [概览](/channels-overview) — Channel 概念和策略 -- [Zalo 个人](/channel-zalo-personal) — 个人 Zalo 账号集成 -- [Telegram](/channel-telegram) — Telegram bot 设置 -- [Browser Pairing](/channel-browser-pairing) — 配对流程 +- [Channels overview](/channels-overview) — DM policies, pairing, message flow +- [Zalo Bot](/channel-zalo-bot) — static-token alternative for small deployments +- [Zalo Personal](/channel-zalo-personal) — reverse-engineered personal account - + diff --git a/zh/llms-full.txt b/zh/llms-full.txt index 6dee222..c5cd28c 100644 --- a/zh/llms-full.txt +++ b/zh/llms-full.txt @@ -9024,124 +9024,9 @@ list_group_members(channel?, chat_id?) → { count, members: [{ member_id, name --- -> 翻译自 [English version](/channel-zalo-oa) -# Zalo OA Channel -Zalo 官方账号(OA)集成。仅支持 DM,基于配对的访问控制,支持图片。 - -## 设置 - -**创建 Zalo OA:** - -1. 前往 https://oa.zalo.me -2. 创建官方账号(需要 Zalo 手机号) -3. 设置 OA 名称、头像和封面照片 -4. 在 OA 设置中,进入"Settings" → "API" → "Bot API" -5. 创建 API key -6. 复制 API key 用于配置 - -**启用 Zalo OA:** - -```json -{ - "channels": { - "zalo": { - "enabled": true, - "token": "YOUR_API_KEY", - "dm_policy": "pairing", - "allow_from": [], - "media_max_mb": 5 - } - } -} -``` - -## 配置 - -所有配置项位于 `channels.zalo`: - -| 配置项 | 类型 | 默认值 | 说明 | -|-----|------|---------|-------------| -| `enabled` | bool | false | 启用/禁用 channel | -| `token` | string | 必填 | 来自 Zalo OA 控制台的 API key | -| `allow_from` | list | -- | 用户 ID 白名单 | -| `dm_policy` | string | `"pairing"` | `pairing`、`allowlist`、`open`、`disabled` | -| `webhook_url` | string | -- | 可选 webhook URL(覆盖轮询) | -| `webhook_secret` | string | -- | 可选 webhook 签名密钥 | -| `media_max_mb` | int | 5 | 最大图片文件大小(MB) | -| `block_reply` | bool | -- | 覆盖 gateway block_reply(nil=继承) | - -## 功能特性 - -### 仅限 DM - -Zalo OA 只支持直接消息。群组功能不可用。所有消息均视为 DM。 - -### 长轮询 - -默认模式:Bot 每 30 秒轮询 Zalo API 获取新消息。服务器返回消息并标记为已读。 - -- 轮询超时:30 秒(默认) -- 错误退避:5 秒 -- 文本限制:每条消息 2,000 字符 -- 图片限制:5 MB - -### Webhook 模式(可选) - -不使用轮询,改为配置 Zalo 将事件 POST 到你的 gateway: - -```json -{ - "webhook_url": "https://your-gateway.com/zalo/webhook", - "webhook_secret": "your_webhook_secret" -} -``` - -Zalo 在请求头 `X-Zalo-Signature` 中发送 HMAC 签名。处理前先验证签名。 - -### 图片支持 - -Bot 可以接收和发送图片(JPG、PNG)。默认最大 5 MB。 - -**接收**:图片在消息处理期间下载并以临时文件保存。 - -**发送**:图片作为媒体附件发送: - -```json -{ - "channel": "zalo", - "content": "Here's your image", - "media": [ - { "url": "/tmp/image.jpg", "type": "image" } - ] -} -``` - -### 默认配对 - -默认 DM 策略为 `"pairing"`。新用户看到配对码说明,带 60 秒防抖(不会被刷屏)。管理员通过以下方式审批: - -``` -/pair CODE -``` - -## 故障排查 - -| 问题 | 解决方案 | -|-------|----------| -| "Invalid API key" | 检查来自 Zalo OA 控制台的 token。确保 OA 处于活跃状态且 Bot API 已启用。 | -| 未收到消息 | 验证轮询是否运行中(检查日志)。确保 OA 可以接收消息(未被暂停)。 | -| 图片上传失败 | 验证图片文件存在且在 `media_max_mb` 以内。检查文件格式(JPG/PNG)。 | -| Webhook 签名不匹配 | 确保 `webhook_secret` 与 Zalo 控制台一致。检查时间戳是否最新。 | -| 配对码未发送 | 检查 DM 策略是否为 `"pairing"`。验证管理员可以向 OA 发送消息。 | - -## 下一步 - -- [概览](/channels-overview) — Channel 概念和策略 -- [Zalo 个人](/channel-zalo-personal) — 个人 Zalo 账号集成 -- [Telegram](/channel-telegram) — Telegram bot 设置 -- [Browser Pairing](/channel-browser-pairing) — 配对流程 +--- diff --git a/zh/llms.txt b/zh/llms.txt index 37be27f..7e4721c 100644 --- a/zh/llms.txt +++ b/zh/llms.txt @@ -62,7 +62,8 @@ - [Discord](zh/channels/discord.md): 通过 Discord Gateway API 集成 Discord bot。支持 DM、服务器、线程和通过消息编辑实现的流式响应。 - [Feishu / Lark](zh/channels/feishu.md): **创建飞书应用:** - [Larksuite](zh/channels/larksuite.md): **创建 Larksuite 应用:** -- [Zalo OA](zh/channels/zalo-oa.md): Zalo 官方账号(OA)集成。仅支持 DM,基于配对的访问控制,支持图片。 +- [Zalo Bot](zh/channels/zalo-bot.md): 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. +- [Zalo OA](zh/channels/zalo-oa.md): Zalo Official Account integration via OAuth v4. Production-ready, multi-OA, auto-refreshing tokens, with both polling (default) and webhook transports. - [Zalo Personal](zh/channels/zalo-personal.md): 使用逆向工程协议(zcago)的非官方个人 Zalo 账号集成。支持 DM 和群组,采用严格访问控制。 - [Slack](zh/channels/slack.md): 通过 Socket Mode(WebSocket)集成 Slack。支持 DM、channel @提及、线程回复、流式输出、表情回应、媒体和消息防抖。 - [WhatsApp](zh/channels/whatsapp.md): 直接集成 WhatsApp。GoClaw 直接连接 WhatsApp 多设备协议 —— 无需外部桥接或 Node.js 服务。认证状态存储在数据库中(PostgreSQL 或 SQLite)。