Zalo QR authorization: как обработать qrcode_expired и принимать подписанные webhooks
Если Zalo QR authorization возвращает qrcode_expired, не используйте старый QR повторно. Вызовите POST /v1/accounts/{account_id}/auth/qr/start заново, затем продолжайте опрашивать POST /v1/accounts/{account_id}/auth/qr/check. Webhook endpoint должен быть создан заранее: события авторизации и последующие message.received сообщения должны иметь место доставки.
Ключевые выводы
- В UnifyPort авторизация Zalo работает только через QR: создайте Zalo messaging account с
auth_mode: "qrcode", без provider credentials заранее. - Сначала зарегистрируйте Webhook. Обновления авторизации и входящие сообщения идут через один event stream.
qrcode_expired— нормальное состояние повторной попытки: снова вызовитеqr/start, покажите новый QR и продолжайте polling.- До разбора JSON проверяйте
X-Device-Signatureпо строкеX-Device-Timestamp + "." + raw body. - Если продукту нужен Zalo Official Account identity или OA-native features, оценивайте официальный OA route; эта статья описывает неофициальный интерфейс UnifyPort для существующего messaging account.
Если вы еще выбираете account model, сначала прочитайте Zalo Official Account API vs personal-account webhook. Для архитектуры поддержки по нескольким каналам полезно держать рядом one webhook for LINE, Zalo, and X.
Что это за flow
В официальной документации Zalo есть Official Account API и OA Webhook. Это правильный путь, когда обязательны OA identity, операционные функции OA Manager или официальный platform relationship.
Flow UnifyPort устроен иначе: вы подключаете Zalo messaging account через QR authorization и получаете normalized events через webhook delivery layer UnifyPort. В Zalo authorization указано, что Zalo использует QR login и требует webhook endpoint для доставки authentication и message events.
Шаг 1: создайте signed Webhook до сканирования
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 endpoint: url должен быть absolute URL, status принимает active или inactive, а subscribed_events принимает конкретные public event names или ["*"]. Для первого теста конкретные события проще отлаживать.
Шаг 2: создайте Zalo messaging account
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 endpoints. Не вставляйте production identifiers в публичные issue trackers или prompts для AI tools.
Шаг 3: запустите QR authorization и обработайте 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 '{}'
Затем опрашивайте endpoint проверки:
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 с экрана и снова вызовите qr/start. Хороший admin UI показывает три состояния: waiting for scan, expired—generate a new QR, authorized.
Шаг 4: проверьте подпись до обработки event
Webhook delivery определяет signed string так:
<X-Device-Timestamp>.<raw request body>
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') {
// Store event.id, event.account_id, event.occurred_at, and event.data before routing.
}
res.sendStatus(202);
});
Если framework уже разобрал JSON до проверки, подпись может не совпасть из-за изменения байтов. Для UnifyPort endpoint используйте raw-body route.
Шаг 5: сохраните normalized Zalo event
{
"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 classification и назначение оператору должны выполняться после durable accept. Общий паттерн описан в webhook-first inbound integration checklist.
FAQ
Что делать при qrcode_expired?
Снова вызовите auth/qr/start, отобразите новый QR и продолжайте polling auth/qr/check. Expired QR использовать нельзя.
Нужны ли Zalo developer credentials?
Для документированного Zalo QR flow в UnifyPort provider credentials заранее не нужны. Identity определяется после сканирования QR нужным пользователем.
Это официальный Zalo OA Webhook?
Нет. Официальный OA Webhook относится к Official Account developer model. Здесь описан неофициальный интерфейс UnifyPort для normalized inbound events.
Следующий шаг
Откройте Zalo authorization и Webhook delivery рядом, подключите test account и отправьте одно входящее сообщение Zalo до подключения Slack, CRM или AI workflow.
Sources
Official sources checked on 2026-09-04:
Превратите интеграцию сообщений в стабильный продуктовый pipeline.
Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.