Zalo QR Authorization: แก้ qrcode_expired และรับ Webhook ที่มีลายเซ็น
ถ้า Zalo QR authorization คืนค่า qrcode_expired อย่านำ QR เดิมกลับมาใช้ ให้เรียก POST /v1/accounts/{account_id}/auth/qr/start ใหม่ แล้ว poll POST /v1/accounts/{account_id}/auth/qr/check ต่อไป ก่อนเริ่มสแกนควรสร้าง Webhook endpoint ไว้แล้ว เพราะสถานะการยืนยันตัวตนและข้อความ message.received จะถูกส่งเข้ามาทาง event stream นี้
สรุปสำคัญ
- ใน UnifyPort การยืนยันตัวตนของ Zalo เป็น QR-only: สร้าง Zalo messaging account ด้วย
auth_mode: "qrcode"โดยไม่ต้องใส่ provider credentials ล่วงหน้า - สร้าง Webhook ก่อนเสมอ ทั้ง auth updates และ inbound messages จะส่งผ่าน event stream เดียวกัน
qrcode_expiredเป็นสถานะ retry ปกติ: เรียกqr/startใหม่ แสดง QR ใหม่ แล้ว poll ต่อ- ตรวจสอบ
X-Device-Signatureด้วยX-Device-Timestamp + "." + raw bodyก่อน parse JSON - ถ้าคุณต้องใช้ Zalo Official Account identity หรือ OA-native features ให้ประเมิน official OA route; บทความนี้ครอบคลุม UnifyPort unofficial interface สำหรับ messaging account ที่มีอยู่แล้ว
ในไทย LINE เป็นช่องทางหลักของหลายทีม แต่ทีมที่ขายหรือซัพพอร์ตลูกค้าเวียดนามมักต้องรวม Zalo เข้า queue เดียวกันด้วย ถ้ายังเลือกระหว่าง OA กับบัญชีที่มีอยู่ ให้อ่าน Zalo Official Account API vs personal-account webhook ก่อน และดูภาพรวมหลายช่องทางใน one webhook for LINE, Zalo, and X
Flow นี้ไม่ใช่ official OA Webhook
เอกสารทางการของ Zalo มี Official Account API และ OA Webhook ซึ่งเหมาะเมื่อธุรกิจต้องใช้ OA identity, OA Manager operations หรือ official platform relationship
UnifyPort ใช้แนวทางคนละแบบ: เชื่อมต่อ Zalo messaging account ด้วย QR authorization แล้วรับ normalized events ผ่าน webhook delivery layer ของ UnifyPort เอกสาร Zalo authorization ระบุว่า Zalo ใช้ QR login และต้องมี webhook endpoint เพื่อรับ authentication และ message events
Step 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 แบบเจาะจงหรือ ["*"] ได้ สำหรับการทดสอบแรก ใช้ event names แบบเจาะจงจะ debug ง่ายกว่า
Step 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 account ID ใน public issue หรือ prompt ของ AI tool
Step 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 '{}'
จากนั้น 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 '{}'
เมื่อเจอ qrcode_expired ให้ทิ้ง QR เดิมและเรียก qr/start ใหม่ UI สำหรับแอดมินควรมีสามสถานะชัดเจน: รอสแกน, หมดอายุให้สร้าง QR ใหม่, และ authorized
Step 4: ตรวจสอบ signature ก่อน route 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);
});
Step 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"
}
}
}
Store first, route second. Slack, CRM, AI classification และ human assignment ควรทำหลังจาก event ถูกเก็บอย่าง durable แล้ว สำหรับ pattern ทั่วไป อ่าน webhook-first inbound integration checklist
FAQ
ถ้า Zalo คืน qrcode_expired ต้องทำอย่างไร?
เรียก auth/qr/start ใหม่ แสดง QR ใหม่ แล้ว poll auth/qr/check ต่อ อย่าใช้ QR ที่หมดอายุ
ต้องใช้ Zalo developer credentials หรือไม่?
ไม่ต้องใส่ provider credentials ล่วงหน้าสำหรับ UnifyPort Zalo QR flow ตัวตนของบัญชีจะถูกพบหลังจากผู้ใช้ที่ถูกต้องสแกน QR
นี่คือ official Zalo OA Webhook ใช่ไหม?
ไม่ใช่ official OA Webhook อยู่ใน Official Account developer model ส่วนบทความนี้อธิบาย UnifyPort unofficial interface สำหรับ normalized inbound events
ขั้นถัดไป
เปิด Zalo authorization และ Webhook delivery คู่กัน จากนั้นเชื่อมต่อ test account และส่ง Zalo inbound message หนึ่งข้อความก่อนต่อ Slack, CRM หรือ AI workflow
Sources
Official sources checked on 2026-09-04:
เปลี่ยนการเชื่อมต่อข้อความให้เป็น pipeline ผลิตภัณฑ์ที่เสถียร
เริ่มจากการส่งผ่าน API เดียว แล้วส่งข้อความขาเข้าทั้งหมดกลับสู่ระบบธุรกิจของคุณด้วย event มาตรฐาน