OpenClaw Zalo 频道接入指南:Bot API 配置、配对授权、群组策略与 Webhook 模式全解析
OpenClaw Zalo 频道接入指南:Bot API 配置、配对授权、群组策略与 Webhook 模式全解析
本篇技术指南面向 OpenClaw 用户与开发者,完整讲解如何通过 Zalo Bot API 将 Zalo 频道接入 OpenClaw Gateway,涵盖插件安装、Bot Token 配置、DM 配对授权、群组访问策略(fail-closed 安全默认)、长轮询与 Webhook 两种消息接收模式、多账号扩展以及 CLI 投递等实战环节。阅读完本文,你将能够独立完成 Zalo 机器人的上线配置,并理解其底层实现机制(源码位于 extensions/zalo),从而快速排查接入过程中的各类问题。
功能定位与当前状态
Zalo 是越南主流的即时通讯应用,其 Bot API 允许 OpenClaw Gateway 以机器人身份运行 1:1 对话。该频道当前的定位如下:
- 状态:experimental(实验性)。私聊(DMs)受支持;群聊(groups)需要显式配置群组策略来控制行为。
- 插件形态:Zalo 以独立插件形式发布,不随核心安装包捆绑。
- 确定性路由:这是该频道最重要的设计特点之一——所有回复都确定性地路由回发起对话的 Zalo 会话,模型永远不会自行选择发送频道。
- 会话模型:私聊共享 Agent 的主会话(main session);群聊通过
groupPolicy+groupAllowFrom提供策略控制,且默认采用 fail-closed 的 allowlist(白名单)行为。
从插件元信息(extensions/zalo/src/channel.ts)可以看到,该频道的 capability 声明为:chatTypes: ["direct", "group"]、media: true、reactions/threads/polls/nativeCommands: false、blockStreaming: true,别名(alias)为 zl,与官方文档中的能力矩阵一一对应。
第一步:安装 Zalo 插件
方式一:CLI 安装
# 从源码检出目录安装(本地 checkout)
openclaw plugins install ./extensions/zalo
# 从 npm 安装(若已发布)
openclaw plugins install @openclaw/zalo
方式二:onboarding 引导安装
在首次配置向导(onboarding)中选择 Zalo,并确认安装提示,系统会自动拉取并安装该插件。
关于插件机制的更多细节,可参考 docs/tools/plugin.md。
第二步:获取并配置 Bot Token
1) 在 Zalo Bot 平台创建机器人
- 打开 https://bot.zaloplatforms.com 并登录。
- 创建一个新的 Bot,并按需配置其设置。
- 复制 Bot Token,其格式形如
12345689:abc-xyz。
说明:文章仅介绍查看与配置方式;该平台的账号注册与 Bot 审核流程以 Zalo 官方平台规则为准。
2) 通过环境变量或配置文件注入 Token
环境变量方式(仅对默认账号生效):
export ZALO_BOT_TOKEN="12345689:abc-xyz"
配置文件方式(最小配置示例):
{
channels: {
zalo: {
enabled: true,
botToken: "12345689:abc-xyz",
dmPolicy: "pairing",
},
},
}
3) 重启 Gateway
完成配置后重启 gateway(或完成 onboarding),Zalo 频道在解析到有效 Token(来自环境变量或配置)后即自动启动。
Token 解析优先级(源码级细节)
从 extensions/zalo/src/token.ts 的实现可以确认,Token 解析遵循如下优先级:
- 账号级
botToken(channels.zalo.accounts.<id>.botToken)→ 来源标记为config; - 账号级
tokenFile(从文件读取,且会拒绝符号链接,见tryReadSecretFileSync的rejectSymlink: true)→ 来源标记为configFile; - 顶层
channels.zalo.botToken/channels.zalo.tokenFile(仅当账号未显式配置 botToken 时); - 环境变量
ZALO_BOT_TOKEN(仅默认账号); - 以上均未命中则返回空 Token,来源为
none。
也就是说,多账号场景下每个账号可以独立使用自己的 Token 或 Token 文件,而顶层配置作为默认值回退。这也是 ZALO_BOT_TOKEN 环境变量"只对默认账号生效"的源码依据。
多账号支持
Zalo 频道原生支持多账号(multi-account),通过 channels.zalo.accounts 配置,每个账号可独立设置 Token、显示名、DM/群组策略与 Webhook:
{
channels: {
zalo: {
enabled: true,
accounts: {
support: {
enabled: true,
name: "Support Bot",
botToken: "12345689:abc-xyz",
dmPolicy: "allowlist",
allowFrom: [123456789],
},
marketing: {
enabled: true,
name: "Marketing Bot",
tokenFile: "/path/to/zalo-token",
groupPolicy: "allowlist",
groupAllowFrom: [987654321],
},
},
},
},
}
账号配置在源码中由 extensions/zalo/src/config-schema.ts 的 buildCatchallMultiAccountChannelSchema 与 extensions/zalo/src/types.ts 的 ZaloAccountConfig 共同定义;运行时由 extensions/zalo/src/accounts.ts 负责账号合并解析(resolveMergedAccountConfig),账号级配置会覆盖顶层同名配置。插件清单 extensions/zalo/openclaw.plugin.json 声明了频道 id 为 zalo。
工作原理(消息流转)
从 extensions/zalo/src/monitor.ts 的运行时实现可以还原完整的消息处理链路:
- 事件归一化:Zalo 平台推送的更新(update)被归一化处理。
processUpdate按event_name分发:message.text.received进入文本处理流程;message.image.received进入图片处理流程(先做授权校验,再下载媒体);message.sticker.received与message.unsupported.received仅记录日志、不触发 Agent 回复。 - 入站封装:消息被封装为共享的 channel envelope(含媒体占位符),并通过
finalizeInboundContext构建带Provider: "zalo"、Surface: "zalo"的上下文,随后记录入站会话(recordInboundSession)。 - 回复回路由:回复管线(
createChannelReplyPipeline)通过deliverZaloReply把回复文本分块(ZALO_TEXT_LIMIT = 2000)后调用sendMessage,或调用sendPhoto发送图片,确保回复始终回到同一 Zalo 会话。回复前还会发送 typing 动作(sendChatAction,超时 5 秒,见ZALO_TYPING_TIMEOUT_MS)。 - 消息接收模式:默认使用 长轮询(long-polling);配置
channels.zalo.webhookUrl后切换为 Webhook 模式。两种模式互斥(详见下文)。
限制与边界
| 限制项 | 说明 |
|---|---|
| 文本分块 | 出站文本按 2000 字符分块发送(Zalo API 限制),常量定义于 extensions/zalo/src/channel.ts 的 zaloTextChunkLimit = 2000 |
| 媒体大小 | 入站/出站媒体下载与上传受 channels.zalo.mediaMaxMb 限制,默认 5 MB(DEFAULT_MEDIA_MAX_MB = 5,见 extensions/zalo/src/monitor.ts) |
| 流式输出 | 默认阻止流式输出(blockStreaming: true),因为 2000 字符分块限制使得流式价值有限 |
访问控制:私聊(DMs)
DM 策略
channels.zalo.dmPolicy 支持四种取值,默认 pairing:
| 取值 | 行为 |
|---|---|
pairing(默认) |
未知发送者会收到一个配对码;消息在批准前被忽略。配对码 1 小时后过期 |
allowlist |
仅允许 channels.zalo.allowFrom 中列出的用户 ID 触发回复 |
open |
开放模式,需配置 allowFrom: ["*"] |
disabled |
完全拒绝所有私聊消息 |
配对(Pairing)流程
Zalo 的 DM 访问默认采用 Pairing 机制(OpenClaw 的"显式所有者批准"机制)。未知用户首次联系机器人时,机器人会下发一个配对码;批准前消息不会被处理。配对码的规则(详见 docs/channels/pairing.md):
- 8 位大写字符,不含易混淆字符(
0O1I); - 1 小时后过期,机器人仅在新请求创建时(大约每发送者每小时一次)下发配对消息;
- 每个频道的待处理配对请求默认上限为 3 个,超出的请求会被忽略,直到某个请求过期或被批准。
批准操作通过 CLI 完成:
openclaw pairing list zalo
openclaw pairing approve zalo <CODE>
配对状态存储于 ~/.openclaw/credentials/ 目录下(pending 请求为 <channel>-pairing.json,已批准的白名单为 <channel>-allowFrom.json 或 <channel>-<accountId>-allowFrom.json)。在源码中,配对挑战由 extensions/zalo/src/monitor.ts 的 createChannelPairingController 下发,批准通知通过 notifyZaloPairingApproval 发送 PAIRING_APPROVED_MESSAGE。
allowFrom 白名单
channels.zalo.allowFrom 接受数字格式的用户 ID(由于 Zalo API 不支持用户名查询,无法通过用户名匹配)。可选的 zalo: / zl: 前缀会被自动剥离(见 extensions/zalo/src/channel.ts 的 normalizeZaloMessagingTarget 与 extensions/zalo/src/group-access.ts 的 ZALO_ALLOW_FROM_PREFIX_RE)。
访问控制:群聊(Groups)
Zalo 群聊支持策略控制,默认 fail-closed(默认拒绝):
| 配置项 | 取值 | 行为 |
|---|---|---|
channels.zalo.groupPolicy |
open / allowlist / disabled |
控制群组入站消息的处理方式,默认 allowlist |
channels.zalo.groupAllowFrom |
用户 ID 列表 | 限制哪些群成员可以触发机器人 |
具体规则:
groupPolicy: "disabled":屏蔽所有群消息;groupPolicy: "open":允许任意群成员触发(受 @提及门控,mention-gated,见 extensions/zalo/src/channel.ts 的groups.resolveRequireMention: () => true);groupAllowFrom未设置时,回退使用allowFrom进行发送者校验;- 运行时若
channels.zalo配置段完全缺失,出于安全考虑仍会回退到groupPolicy="allowlist"(fail-closed 兜底,见 extensions/zalo/src/group-access.ts 的resolveZaloRuntimeGroupPolicy)。
此外,若将 groupPolicy 设为 open 却同时配置了 groupAllowFrom/allowFrom 白名单,或 open 且无任何白名单,extensions/zalo/src/channel.ts 的 collectZaloSecurityWarnings 会分别产生安全警告,提示你改用 allowlist + groupAllowFrom 组合,或在明知风险的情况下保持开放。
长轮询 vs Webhook
长轮询(默认)
- 无需公网 URL,开箱即用。
- 源码层面(extensions/zalo/src/monitor.ts)轮询超时设为 30 秒(
pollTimeout = 30),Zalo API 的错误码 408 被识别为轮询超时(无新更新时静默忽略,见 extensions/zalo/src/api.ts 的isPollingTimeout)。 - 进入轮询模式前,插件会先检查并清理已配置的 Webhook(部分 Zalo 环境不开放 webhook 检查接口时,以 404 容忍并继续)。
Webhook 模式
配置以下三项即可启用:
{
channels: {
zalo: {
enabled: true,
botToken: "12345689:abc-xyz",
webhookUrl: "https://example.com/zalo-webhook",
webhookSecret: "your-secret-8-plus-chars",
webhookPath: "/zalo-webhook",
},
},
}
Webhook 模式的硬性约束:
- Webhook URL 必须使用 HTTPS(源码在启动时校验
webhookUrl.startsWith("https://")); - Secret 长度必须为 8–256 字符;
- Zalo 通过
X-Bot-Api-Secret-Token请求头携带 secret,Gateway 侧使用timingSafeEqual做常数时间比较防止时序攻击(extensions/zalo/src/monitor.webhook.ts); - Gateway HTTP 服务器在
channels.zalo.webhookPath处理 Webhook 请求;未显式配置时默认取 Webhook URL 的路径(resolveWebhookPath); - 请求必须使用
Content-Type: application/json(或+json媒体类型),请求体上限 1 MB; - 重复事件去重:以
event_name + message_id为键,在 5 分钟重放窗口(ZALO_WEBHOOK_REPLAY_WINDOW_MS = 5 * 60_000)内忽略重复事件; - 突发流量限流:按 path/source(客户端 IP)做固定窗口限流,超限可能返回 HTTP 429;
- 启动 Webhook 前会调用
setWebhook注册;停止时调用deleteWebhook清理(清理超时 5 秒)。
重要:根据 Zalo API 官方文档,getUpdates(轮询)与 Webhook 互斥,二者不可同时启用。插件在进入轮询模式时会主动清理已有 Webhook,反之亦然。
支持的消息类型
| 消息类型 | 处理方式 |
|---|---|
| 文本消息(Text) | 完整支持,按 2000 字符分块发送 |
| 图片消息(Image) | 入站:下载并处理(受 mediaMaxMb 限制);出站:通过 sendPhoto 发送(extensions/zalo/src/api.ts) |
| 贴纸(Stickers) | 记录日志但不完整处理(无 Agent 响应) |
| 不支持的类型 | 记录日志(例如来自受保护用户的消息) |
能力矩阵
| 功能 | 状态 |
|---|---|
| 私聊(Direct messages) | ✅ 支持 |
| 群聊(Groups) | ⚠️ 支持,带策略控制(默认 allowlist) |
| 媒体(图片) | ✅ 支持 |
| 表情回应(Reactions) | ❌ 不支持 |
| 线程(Threads) | ❌ 不支持 |
| 投票(Polls) | ❌ 不支持 |
| 原生命令(Native commands) | ❌ 不支持 |
| 流式输出(Streaming) | ⚠️ 已阻止(2000 字符限制) |
使用 CLI/Cron 投递消息
Zalo 作为确定性路由频道,同样支持通过 CLI 或定时任务(cron)直接投递消息。投递目标使用 chat id:
openclaw message send --channel zalo --target 123456789 --message "hi"
目标解析细节(extensions/zalo/src/session-route.ts):
- 支持
zalo:与zl:前缀(会被剥离); - 以
group:开头的目标会被识别为群聊(zalo:group:<chatId>),否则视为私聊(zalo:<chatId>); - CLI 目标提示符为
<chatId>,且要求目标为数字 ID(isNumericTargetId)。
Troubleshooting 排查指南
Bot 不响应:
# 1) 校验 Token 是否有效(探测 getMe)
openclaw channels status --probe
# 2) 确认发送者已获授权(pairing 或 allowFrom 白名单)
# 3) 查看 Gateway 日志
openclaw logs --follow
从源码看,启动时插件会先调用 probeZalo(即 getMe)校验 Token 并获取 Bot 名称(extensions/zalo/src/probe.ts),探测失败只会告警、不会阻止启动——因此"启动了但 Token 无效"时,日志与 --probe 是最直接的判断手段。
Webhook 收不到事件:
- 确认 Webhook URL 使用 HTTPS;
- 确认 Secret 长度为 8–256 字符;
- 确认 Gateway 的 HTTP 端点在被配置的路径上可达;
- 检查是否仍有 getUpdates 轮询在运行(二者互斥)。
配置参考(Zalo)
完整频道配置总览见 docs/gateway/configuration.md。以下为 Zalo 频道的全部配置项:
Provider 级选项
| 配置项 | 说明 |
|---|---|
channels.zalo.enabled |
启用/停用频道启动 |
channels.zalo.botToken |
来自 Zalo Bot 平台的 Bot Token |
channels.zalo.tokenFile |
从文件路径读取 Token |
channels.zalo.dmPolicy |
pairing | allowlist | open | disabled(默认:pairing) |
channels.zalo.allowFrom |
DM 白名单(用户 ID)。open 模式需要 "*"。向导会要求输入数字 ID |
channels.zalo.groupPolicy |
open | allowlist | disabled(默认:allowlist) |
channels.zalo.groupAllowFrom |
群组发送者白名单(用户 ID)。未设置时回退到 allowFrom |
channels.zalo.mediaMaxMb |
入站/出站媒体上限(MB,默认 5) |
channels.zalo.webhookUrl |
启用 Webhook 模式(必须 HTTPS) |
channels.zalo.webhookSecret |
Webhook Secret(8–256 字符) |
channels.zalo.webhookPath |
Gateway HTTP 服务器上的 Webhook 路径 |
channels.zalo.proxy |
API 请求使用的代理 URL |
channels.zalo.responsePrefix |
该频道/账号的出站回复前缀覆盖(源码 extensions/zalo/src/types.ts 支持) |
多账号选项
| 配置项 | 说明 |
|---|---|
channels.zalo.accounts.<id>.botToken |
单账号 Token |
channels.zalo.accounts.<id>.tokenFile |
单账号 Token 文件 |
channels.zalo.accounts.<id>.name |
显示名称(用于 CLI/UI 列表) |
channels.zalo.accounts.<id>.enabled |
启用/停用该账号(默认 true) |
channels.zalo.accounts.<id>.dmPolicy |
单账号 DM 策略 |
channels.zalo.accounts.<id>.allowFrom |
单账号 DM 白名单 |
channels.zalo.accounts.<id>.groupPolicy |
单账号群组策略 |
channels.zalo.accounts.<id>.groupAllowFrom |
单账号群组发送者白名单 |
channels.zalo.accounts.<id>.webhookUrl |
单账号 Webhook URL |
channels.zalo.accounts.<id>.webhookSecret |
单账号 Webhook Secret |
channels.zalo.accounts.<id>.webhookPath |
单账号 Webhook 路径 |
channels.zalo.accounts.<id>.proxy |
单账号代理 URL |
提示:无论通过哪种方式修改配置,都需要重启 Gateway 才会生效(插件声明了
reload.configPrefixes: ["channels.zalo"])。
进一步阅读
- Zalo 频道插件源码:extensions/zalo(含单元测试与契约测试,如 group-policy.contract.test.ts、monitor.webhook.test.ts、send.test.ts,可作为行为验证依据)
- 插件安装与管理:docs/tools/plugin.md
- 配对机制详解:docs/channels/pairing.md
- 通用频道配置:docs/gateway/configuration.md
- 其他频道文档:docs/channels/index.md