OpenClaw 接入 Zalo 渠道:Bot API 插件安装、配置与 Webhook 实战指南
OpenClaw 接入 Zalo 渠道:Bot API 插件安装、配置与 Webhook 实战指南
导读
Zalo 是越南主流的即时通讯平台,其 Bot API 允许开发者创建机器人并进行一对一的私聊对话。OpenClaw(本仓库以 openclaw-cn 中文社区版形式维护)将 Zalo 以独立插件(@clawdbot/zalo)的方式提供,通过 gateway 承载 Zalo 频道,实现消息的收发、配对授权与确定性路由。读完本文,你将掌握:如何从本地源码或 npm 安装 Zalo 插件、如何配置 bot token 与访问策略、如何启用 Webhook 模式并理解网关端的安全校验机制,以及长轮询与 Webhook 两种接收模式各自的适用场景。
一、插件是什么:Zalo 频道的定位与能力边界
Zalo 频道插件位于仓库 extensions/zalo/ 目录,其 package.json 中声明渠道元信息:渠道 ID 为 zalo,别名为 zl,在渠道选择列表中显示为 "Zalo (Bot API)",简介为 "Vietnam-focused messaging platform with Bot API"(extensions/zalo/package.json)。
从插件入口 extensions/zalo/index.ts 可以看到,插件在注册时会执行三件事:
- 保存插件运行时句柄(
setZaloRuntime); - 向核心注册频道插件与频道停靠点(
registerChannel); - 注册 HTTP 处理器,用于接管 gateway HTTP 服务器上的 Webhook 请求(
registerHttpHandler)。
插件声明的能力集(位于 extensions/zalo/src/channel.ts)明确界定了其边界:
| 能力 | 支持情况 |
|---|---|
| 私聊(DM) | ✅ 支持 |
| 群聊 | ❌ 暂不支持(等待 Zalo Bot API 开放) |
| 媒体(图片) | ✅ 支持 |
| Reactions / Threads / Polls / 原生命令 | ❌ 均不支持 |
| 流式输出(streaming) | ⚠️ 默认屏蔽(受 2000 字符限制影响) |
关键行为特性包括:
- 确定性路由:所有回复都回到发起对话的同一个 Zalo 会话,模型不会自行选择渠道;
- 共享会话:Zalo 私聊复用 agent 的主会话,不额外创建独立会话;
- 长文本分块:出站文本按 2000 字符切块发送(Zalo API 限制);
- 配对模式:私聊访问默认走 pairing(配对)授权流程。
二、安装插件:本地 checkout 与 npm 两种方式
2.1 从本地源码 checkout 安装
在本仓库源码目录下执行:
openclaw-cn plugins install ./extensions/zalo
这种方式的优点是直接使用仓库内的最新实现,适合二次开发、调试或离线环境。
2.2 从 npm 安装
openclaw-cn plugins install @clawdbot/zalo
插件元数据中声明了安装来源:npm 包名为 @clawdbot/zalo,本地路径为 extensions/zalo,默认安装选择为 npm(见 extensions/zalo/package.json)。
2.3 通过 Onboarding 交互式安装
在 onboarding 流程中选择 Zalo,并在安装确认提示中确认,即可自动拉取插件。这适合首次安装时使用向导快速完成渠道接入。
三、获取 Bot Token:Zalo Bot Platform
接入 Zalo Bot API 前,需要先在 Zalo 官方机器人平台创建机器人并获取 token:
- 打开 https://bot.zaloplatforms.com 并登录;
- 创建新的 Bot 并完成基础设置;
- 复制生成的 Bot Token(格式形如
12345689:abc-xyz)。
插件源码中的提示逻辑(extensions/zalo/src/onboarding.ts)会在引导时展示上述步骤,并提醒也可以使用环境变量 ZALO_BOT_TOKEN 注入 token。
四、最小配置:启用频道与设置 token
4.1 基础配置示例
在配置文件(JSON5 格式)中写入:
{
channels: {
zalo: {
enabled: true,
botToken: "12345689:abc-xyz",
dmPolicy: "pairing",
proxy: "http://proxy.local:8080"
}
}
}
各字段含义:
enabled:是否在启动时拉起该频道;botToken:从 Zalo Bot Platform 获取的机器人 token;dmPolicy:私聊访问策略,默认pairing(详见下文"访问控制");proxy:访问 Zalo API 时使用的代理地址,适用于国内网络环境等需要代理的场景。
4.2 Token 的三种注入来源
插件的 token 解析逻辑位于 extensions/zalo/src/token.ts,解析优先级(针对默认账户)为:
- 配置文件:
channels.zalo.botToken; - Token 文件:
channels.zalo.tokenFile指向的文件内容(自动trim()); - 环境变量:
ZALO_BOT_TOKEN(仅对默认账户生效)。
如果启用多账户,resolveZaloToken 会优先读取 channels.zalo.accounts.<id>.botToken 或对应的 tokenFile,再回退到顶层配置。token 解析结果会标记来源(env / config / configFile / none),用于 openclaw-cn channels status 等诊断命令展示。
4.3 多账户支持
在默认账户之外,可通过 channels.zalo.accounts 配置多个 Zalo 机器人账户,每个账户拥有独立的 token、访问策略、Webhook 与代理配置:
{
channels: {
zalo: {
enabled: true,
defaultAccount: "support-bot",
accounts: {
"support-bot": {
name: "Support Bot",
enabled: true,
botToken: "123456789:aaa-bbb",
dmPolicy: "allowlist",
allowFrom: ["100010001", "100020002"]
},
"news-bot": {
name: "News Bot",
botToken: "987654321:ccc-ddd"
}
}
}
}
}
账户解析逻辑(extensions/zalo/src/accounts.ts)会先将账户配置与顶层基础配置合并(账户级字段优先),再判断 enabled、解析 token 与 defaultAccount。
五、访问控制:私聊的四种策略
Zalo 插件的配置模式(extensions/zalo/src/config-schema.ts)中,dmPolicy 支持四种取值:
| 取值 | 行为 |
|---|---|
pairing(默认) |
未知发送者会收到一个配对码,消息被忽略直到配对被批准;配对码 1 小时后过期 |
allowlist |
仅接受 allowFrom 列表中已批准的用户 ID |
open |
接受任意用户消息(要求 allowFrom 中包含 "*") |
disabled |
完全拒绝私聊消息 |
allowFrom 接受数字形式的 Zalo 用户 ID(插件没有用户名查询能力)。该字段支持 zalo: 与 zl: 前缀,插件会在比较时自动归一化去除前缀(见 extensions/zalo/src/monitor.ts)。
5.1 批准配对
默认策略下,当陌生用户首次给机器人发消息时,机器人会回复一条包含配对码与用户 ID 的提示。管理员可通过 CLI 批准:
openclaw-cn pairing list zalo
openclaw-cn pairing approve zalo <CODE>
5.2 安全提示:避免使用 open 策略
插件的状态诊断模块(extensions/zalo/src/status-issues.ts)会在 dmPolicy 为 open 时向 openclaw-cn channels status 报告一条配置类告警,提示"任何用户都可以不经配对直接给机器人发消息",并建议改为 pairing 或 allowlist。这属于内置的主动安全审计,值得在部署时留意。
六、消息收发机制:长轮询与 Webhook
Zalo 插件支持两种接收更新的模式,二者互斥(Zalo API 文档明确二者不可同时使用)。启动时 monitorZaloProvider 会根据是否配置 webhookUrl 自动选择模式(extensions/zalo/src/monitor.ts)。
6.1 长轮询模式(默认)
不配置 webhookUrl 时,插件调用 Zalo Bot API 的 getUpdates 接口进行长轮询:
- 单次轮询超时 30 秒,接口调用内部超时设为
30 + 5秒; - 轮询返回单个更新(与 Telegram 返回数组不同,源码注释已注明此差异);
- 收到
408错误码视为正常超时(无新消息),不报错; - 适合无需公网 URL 的场景,是默认推荐方式。
6.2 Webhook 模式
在需要低延迟推送或无法长时间占用轮询连接时,可启用 Webhook:
{
channels: {
zalo: {
webhookUrl: "https://example.com/zalo-webhook",
webhookSecret: "your-secret-8-plus-chars",
webhookPath: "/zalo-webhook"
}
}
}
Webhook 模式启动时会做如下校验与动作(见 extensions/zalo/src/monitor.ts):
webhookUrl与webhookSecret必填;webhookUrl必须为https://开头;webhookSecret长度必须在 8~256 字符之间;- 调用
setWebhook将回调地址与 secret 注册到 Zalo; - 在 gateway 的 HTTP 服务器上注册请求处理器,路径解析规则为:
- 若配置了
webhookPath,则使用该路径(自动补全前导/、去除尾部/); - 若省略
webhookPath,则从webhookUrl中提取 URL 路径作为监听路径;
- 若配置了
- 在 gateway 停止(abort 信号触发)时调用
deleteWebhook清理注册。
配置变更后需重启 gateway 才会生效。
6.3 Webhook 请求的安全校验
Webhook 请求处理器(extensions/zalo/src/monitor.ts)在把事件交给 agent 之前做了多层防护,这些细节对线上安全至关重要:
- 方法限制:仅接受
POST,其他方法返回405 Method Not Allowed; - 恒定时间比较:通过请求头
X-Bot-Api-Secret-Token与配置的webhookSecret做timingSafeEqual校验,长度不匹配时也会填充后执行比较,避免通过时序推断 secret(返回401 unauthorized); - 限流:按"路径 + 来源 IP"维度限流,窗口为 60 秒,最多 120 次请求,超限返回
429 Too Many Requests; - Content-Type 校验:非
application/json(或+json)返回415 Unsupported Media Type; - 体积限制:请求体最大 1 MB,超限返回
413; - 重放去重:按
event_name + message_id在 5 分钟内去重,重复事件直接返回200 ok但不再进入消息处理管线。
上述防护均有对应单元测试覆盖(extensions/zalo/src/monitor.webhook.test.ts),例如非 JSON content-type 返回 415、重复事件只触发一次入站、超限请求返回 429 等,可直接运行验证。
七、入站消息处理与回复路由
7.1 支持的消息类型
| 消息类型 | 处理方式 |
|---|---|
文本(message.text.received) |
完整支持,进入 agent 对话管线 |
图片(message.image.received) |
下载图片并作为媒体附件进入管线(caption 作为文本) |
贴纸(message.sticker.received) |
仅记录日志,不触发 agent 回复 |
其他不支持类型(message.unsupported.received) |
仅记录日志 |
图片入站处理(extensions/zalo/src/monitor.ts)会先下载远程图片,再通过核心的媒体存储机制按 mediaMaxMb(默认 5 MB)上限保存,之后连同 caption 一起进入标准入站管线。
7.2 入站消息的标准管线
文本与图片消息最终都进入 processMessageWithPipeline(extensions/zalo/src/monitor.ts),完成以下工作:
- 依据
dmPolicy与allowFrom做访问控制(配对/白名单判断); - 依据群聊/私聊与访问策略计算命令授权(
CommandAuthorized); - 通过
resolveAgentRoute确定消息应路由到的 agent 与会话; - 将消息归一化为核心标准入站信封(channel envelope),携带
From、To、SessionKey、Provider: "zalo"、Surface: "zalo"等上下文; - 记录入站会话并调用
dispatchReplyWithBufferedBlockDispatcher分发到 agent 处理。
7.3 出站回复:分块与媒体
出站回复由 deliverZaloReply 完成(extensions/zalo/src/monitor.ts):
- 文本按 2000 字符分块发送(支持按换行/空格优先断行的智能分块);
- 若回复包含媒体 URL,则通过
sendPhoto发送图片,并把文本作为首张图片的 caption; - Markdown 表格会被转换为更兼容的表格模式后发送;
- 每条消息发送成功后更新
lastOutboundAt状态,供渠道状态页展示。
底层 API 封装位于 extensions/zalo/src/api.ts:统一 POST 到 https://bot-api.zaloplatforms.com/bot<token>/<method>,提供 getMe、sendMessage、sendPhoto、getUpdates、setWebhook、deleteWebhook、getWebhookInfo 等接口,并在 ok: false 时抛出携带 error_code 的 ZaloApiError。
八、CLI 投递目标与运维命令
8.1 直接发送消息(CLI / cron)
可以使用 chat id 作为目标,通过消息命令直接投递:
openclaw-cn message send --channel zalo --target 123456789 --message "hi"
目标解析规则支持 zalo: 与 zl: 前缀,且纯数字 ID(3 位及以上)会被识别为 Zalo chat id(见 extensions/zalo/src/channel.ts)。
8.2 状态诊断
# 探测 token 有效性并展示渠道状态
openclaw-cn channels status --probe
# 查看 gateway 日志
openclaw-cn logs --follow
插件通过 probeZalo(调用 getMe)验证 token 并获取机器人昵称与头像(extensions/zalo/src/probe.ts),状态页会展示 token 来源、运行模式(webhook / polling)、最近入站/出站时间与 DM 策略等信息。
8.3 代理支持
proxy 配置通过 undici 的 ProxyAgent 实现,代理 fetcher 会按代理 URL 缓存复用(extensions/zalo/src/proxy.ts)。API 调用、探针与 Webhook 注册均会使用该 fetcher,适用于需要代理访问 Zalo API 的网络环境。
九、常见问题排查
9.1 机器人不回复
- 用
openclaw-cn channels status --probe确认 token 有效; - 确认发送者已通过配对或
allowFrom白名单授权; - 检查 gateway 日志:
openclaw-cn logs --follow。
9.2 Webhook 收不到事件
- 确认
webhookUrl使用 HTTPS; - 确认
webhookSecret长度在 8~256 字符之间; - 确认 gateway HTTP 服务器在
webhookPath(或 webhook URL 路径)上可达; - 确认长轮询未在运行(二者互斥)。
9.3 性能与限制提示
- 出站文本 2000 字符分块;
- 入站媒体默认上限 5 MB(
mediaMaxMb可调); - 流式输出默认屏蔽,长文本以分块形式发送。
十、关联文档与深入阅读
- 插件 README:extensions/zalo/README.md
- 官方渠道文档:docs/channels/zalo.md
- 插件实现入口:extensions/zalo/index.ts
- 渠道核心实现:extensions/zalo/src/channel.ts
- Webhook 与轮询实现:extensions/zalo/src/monitor.ts
- API 客户端封装:extensions/zalo/src/api.ts
- 配置模式定义:extensions/zalo/src/config-schema.ts
- Webhook 安全防护测试:extensions/zalo/src/monitor.webhook.test.ts
- 插件变更记录:extensions/zalo/CHANGELOG.md
提示:修改
channels.zalo相关配置后,务必重启 gateway 使配置生效;如配置了webhookUrl,请确认getUpdates轮询已停止,以避免两种模式冲突。