← 所有文章
教程

Zalo QR 授权:处理 qrcode_expired 并接收签名 Webhook

如果 Zalo QR 授权返回 qrcode_expired,不要继续使用旧二维码。重新调用 POST /v1/accounts/{account_id}/auth/qr/start,再轮询 POST /v1/accounts/{account_id}/auth/qr/check,并确保 Webhook 端点已经创建好。账号完成授权后,Zalo 入站消息会以签名的 message.received 事件送到你的服务。

要点

  • 在 UnifyPort 中,Zalo 授权只使用 QR:创建 Zalo 消息账号时设置 auth_mode: "qrcode",前置不需要 provider 凭证。
  • 先注册 Webhook。授权状态和后续消息都会通过事件流投递。
  • qrcode_expired 是正常的重试状态:重新调用 qr/start,展示新的 QR 内容,再继续轮询。
  • 解析 JSON 前,先用 X-Device-Timestamp + "." + raw body 验证 X-Device-Signature
  • 如果你的业务必须使用 Zalo Official Account 身份和 OA 能力,请走官方 OA 路线;本文讲的是 UnifyPort 面向现有消息账号的非官方接口。

如果还在选择账号模型,先看 Zalo Official Account API 与个人账号 Webhook 对比。做东南亚多渠道客服时,也可以对照 LINE、Zalo、X 共用一个 Webhook 的架构思路。

这个流程适合什么场景

Zalo 官方开发文档提供 Official Account API 和 OA Webhook,这适合需要 OA 身份、OA 后台运营或官方平台关系的团队。

UnifyPort 的路径不同:你通过 QR 授权连接一个 Zalo 消息账号,然后由 UnifyPort 的 Webhook 投递层输出标准化事件。Zalo 授权文档 明确说明该流程是 QR 登录,并且需要 Webhook 端点来接收授权和消息事件。

第 1 步:扫码前先创建签名 Webhook

先准备一个稳定的 HTTPS 接收地址。第一次调试建议只订阅授权状态和 message.received

curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://inbox.example.com/unifyport/zalo",
  "status": "active",
  "subscribed_events": ["account.auth.succeeded", "account.auth.required", "message.received"],
  "signing_secret": "zalo-support-2026"
}'

字段细节见 Create webhook endpointurl 必须是绝对 URL,status 可为 activeinactivesubscribed_events 可使用明确的公共事件名或 ["*"]

第 2 步:创建 Zalo 消息账号

curl -X POST https://api.unifyport.ai/v1/accounts \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "name": "Zalo Support Inbox",
  "provider": "zalo",
  "region": "global",
  "status": "active",
  "auth_mode": "qrcode",
  "capabilities": ["receive_message"],
  "provider_data": {},
  "metadata": { "workflow": "support-intake" }
}'

保存返回的 account_id,后续 QR 授权接口都需要它。生产账号 ID 不要贴到公开 issue 或 AI 对话中。

第 3 步:启动 QR 授权并处理 qrcode_expired

curl -X POST "https://api.unifyport.ai/v1/accounts/$ACCOUNT_ID/auth/qr/start" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

继续轮询:

curl -X POST "https://api.unifyport.ai/v1/accounts/$ACCOUNT_ID/auth/qr/check" \
  -H "X-Api-Key: $UNIFYPORT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'

一旦状态显示 qrcode_expired,丢弃旧二维码,重新调用 qr/start。后台页面最好只显示三个状态:等待扫码、已过期需刷新、已授权。

第 4 步:验证签名后再处理事件

Webhook delivery 定义的签名字符串是:

<X-Device-Timestamp>.<raw request body>

Express 示例:

import crypto from 'node:crypto';
import express from 'express';

const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;

app.post('/unifyport/zalo', express.raw({ type: 'application/json' }), (req, res) => {
  const timestamp = req.get('X-Device-Timestamp') ?? '';
  const signature = req.get('X-Device-Signature') ?? '';

  const expected = crypto
    .createHmac('sha256', signingSecret)
    .update(timestamp + '.')
    .update(req.body)
    .digest('hex');

  if (signature.length !== expected.length) return res.sendStatus(401);
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString('utf8'));
  if (event.type === 'message.received' && event.provider === 'zalo') {
    // 先持久化 event.id、event.account_id、event.occurred_at 和 event.data,再路由。
  }

  res.sendStatus(202);
});

第 5 步:保存标准化 Zalo 事件

{
  "id": "evt_2f9c1a4b7e",
  "type": "message.received",
  "provider": "zalo",
  "account_id": "acc_8c21d0",
  "occurred_at": "2026-06-08T12:34:56Z",
  "data": {
    "conversation": { "id": "5005", "type": "user" },
    "sender": { "id": "4004", "type": "user", "name": "Minh Nguyen" },
    "message": {
      "id": "3003",
      "text": "Sản phẩm này còn hàng không?",
      "direction": "inbound",
      "sent_at": "2026-06-08T12:34:55Z"
    }
  }
}

先存储,再路由。Slack、CRM、AI 分类和人工分配都应在事件持久化后异步执行。通用接收器设计可参考 Webhook-first 入站集成清单

FAQ

Zalo 返回 qrcode_expired 怎么办?

重新调用 auth/qr/start,展示新的 QR 内容,并继续轮询 auth/qr/check。不要复用过期二维码。

这个流程需要 Zalo 开发者凭证吗?

不需要前置 provider 凭证。账号身份会在指定用户扫码后被识别。

这是 Zalo 官方 OA Webhook 吗?

不是。官方 OA Webhook 属于 Official Account 开发模型;本文描述的是 UnifyPort 非官方接口下的标准化消息事件。

下一步

Zalo authorizationWebhook delivery 放在一起看,先连一个测试账号并发送一条入站 Zalo 消息,再接入下游自动化。

来源

官方来源核对于 2026-09-04:

UnifyPort API

让消息接入变成一条稳定的产品管线。

先用统一 API 跑通发送,再用标准事件把所有入站消息接回业务系统。