Zalo QR Authorization: xử lý qrcode_expired và nhận Webhook đã ký
Nếu Zalo QR authorization trả về qrcode_expired, đừng dùng lại mã QR cũ. Hãy gọi lại POST /v1/accounts/{account_id}/auth/qr/start, rồi tiếp tục poll POST /v1/accounts/{account_id}/auth/qr/check. Webhook endpoint nên được tạo trước khi quét, vì trạng thái xác thực và các sự kiện message.received sau đó đều cần một nơi để được gửi đến.
Điểm chính
- Trong UnifyPort, Zalo authorization là QR-only: tạo Zalo messaging account với
auth_mode: "qrcode", không cần provider credentials trước khi quét. - Đăng ký Webhook trước. Auth updates và inbound messages cùng đi qua event stream.
qrcode_expiredlà trạng thái retry bình thường: gọi lạiqr/start, hiển thị QR mới và tiếp tục polling.- Trước khi parse JSON, xác thực
X-Device-SignaturebằngX-Device-Timestamp + "." + raw body. - Nếu sản phẩm bắt buộc dùng Zalo Official Account identity hoặc OA-native features, hãy chọn official OA route. Bài này nói về UnifyPort unofficial interface cho messaging account hiện có.
Ở Việt Nam, nhiều team vừa cần Zalo cho khách nội địa vừa cần WhatsApp cho khách quốc tế. Nếu bạn còn đang chọn account model, hãy đọc Zalo Official Account API vs personal-account webhook trước. Nếu muốn gom LINE, Zalo và X vào cùng một hàng đợi hỗ trợ, xem thêm one webhook for LINE, Zalo, and X.
Flow này không phải official OA Webhook
Tài liệu chính thức của Zalo có Official Account API và OA Webhook. Đây là đường phù hợp khi bạn cần OA identity, vận hành qua OA Manager, hoặc quan hệ hỗ trợ chính thức với nền tảng.
Flow của UnifyPort khác: bạn kết nối Zalo messaging account bằng QR authorization, rồi nhận normalized events qua webhook delivery layer của UnifyPort. Tài liệu Zalo authorization nêu rằng Zalo dùng QR login và cần webhook endpoint để nhận authentication và message events.
Bước 1: tạo signed Webhook trước khi quét
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"
}'
Chi tiết nằm ở Create webhook endpoint: url phải là absolute URL, status là active hoặc inactive, và subscribed_events nhận public event names cụ thể hoặc ["*"]. Khi test lần đầu, event names cụ thể dễ debug hơn.
Bước 2: tạo 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" }
}'
Lưu account_id trả về. Các QR endpoints bên dưới cần giá trị này; đừng dán production account IDs vào issue công khai hoặc AI prompt.
Bước 3: bắt đầu QR authorization và xử lý 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 '{}'
Sau đó poll:
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 '{}'
Khi nhận qrcode_expired, bỏ QR cũ khỏi màn hình và gọi lại qr/start. Một admin UI tốt nên có ba trạng thái rõ ràng: chờ quét, đã hết hạn—tạo QR mới, và đã authorized.
Bước 4: xác thực signature trước khi xử lý event
Webhook delivery định nghĩa signed string là:
<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);
});
Nếu framework parse JSON trước đoạn kiểm tra này, signature có thể fail vì raw bytes đã thay đổi. Hãy dùng raw-body route cho endpoint của UnifyPort.
Bước 5: lưu 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"
}
}
}
Store first, route second. Slack, CRM, AI classification và phân công nhân sự nên chạy sau khi event đã được lưu bền vững. Pattern tổng quát nằm trong webhook-first inbound integration checklist.
FAQ
Zalo trả về qrcode_expired thì làm gì?
Gọi lại auth/qr/start, hiển thị QR mới và tiếp tục poll auth/qr/check. Không dùng lại QR đã hết hạn.
Flow này có cần Zalo developer credentials không?
Không cần provider credentials trước cho Zalo QR flow được tài liệu hóa trong UnifyPort. Identity được xác định sau khi đúng người dùng quét QR.
Đây có phải official Zalo OA Webhook không?
Không. Official OA Webhook thuộc Official Account developer model. Bài này mô tả UnifyPort unofficial interface để nhận normalized inbound events.
Bước tiếp theo
Mở Zalo authorization cùng Webhook delivery, kết nối test account và gửi một Zalo inbound message trước khi nối Slack, CRM hoặc AI workflow.
Sources
Official sources checked on 2026-09-04:
Biến tích hợp nhắn tin thành một pipeline sản phẩm ổn định.
Bắt đầu bằng cách gửi qua một API, rồi đưa mọi tin nhắn inbound trở lại hệ thống kinh doanh bằng sự kiện chuẩn.