OpenClaw Zalo 频道接入指南:Bot API 配置、配对授权、群组策略与 Webhook 模式全解析

原创2026-10-14 22:46:411,354 阅读
文章标签:人工智能大模型AI 应用后端LLM 网关即时通讯AI Agent插件系统本地部署MCP Clients

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 平台创建机器人

  1. 打开 https://bot.zaloplatforms.com 并登录。
  2. 创建一个新的 Bot,并按需配置其设置。
  3. 复制 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 解析遵循如下优先级:

  1. 账号级 botToken(channels.zalo.accounts.<id>.botToken)→ 来源标记为 config;
  2. 账号级 tokenFile(从文件读取,且会拒绝符号链接,见 tryReadSecretFileSync 的 rejectSymlink: true)→ 来源标记为 configFile;
  3. 顶层 channels.zalo.botToken / channels.zalo.tokenFile(仅当账号未显式配置 botToken 时);
  4. 环境变量 ZALO_BOT_TOKEN(仅默认账号);
  5. 以上均未命中则返回空 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 的运行时实现可以还原完整的消息处理链路:

  1. 事件归一化:Zalo 平台推送的更新(update)被归一化处理。processUpdate 按 event_name 分发:message.text.received 进入文本处理流程;message.image.received 进入图片处理流程(先做授权校验,再下载媒体);message.sticker.received 与 message.unsupported.received 仅记录日志、不触发 Agent 回复。
  2. 入站封装:消息被封装为共享的 channel envelope(含媒体占位符),并通过 finalizeInboundContext 构建带 Provider: "zalo"、Surface: "zalo" 的上下文,随后记录入站会话(recordInboundSession)。
  3. 回复回路由:回复管线(createChannelReplyPipeline)通过 deliverZaloReply 把回复文本分块(ZALO_TEXT_LIMIT = 2000)后调用 sendMessage,或调用 sendPhoto 发送图片,确保回复始终回到同一 Zalo 会话。回复前还会发送 typing 动作(sendChatAction,超时 5 秒,见 ZALO_TYPING_TIMEOUT_MS)。
  4. 消息接收模式:默认使用 长轮询(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"])。

进一步阅读

登录后查看全文
openclaw-zero-token