← Все статьи
Руководство

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:

UnifyPort API

Превратите интеграцию сообщений в стабильный продуктовый pipeline.

Начните с отправки через единый API, затем возвращайте входящие сообщения в бизнес-систему стандартными событиями.