Tích hợp inbound với UnifyPort: hãy bắt đầu từ Webhook
Nếu đội của bạn cần nhận tin nhắn từ WhatsApp, Zalo, LINE hoặc Telegram vào hệ thống hỗ trợ, thành phần production đầu tiên nên là webhook receiver, không phải kết nối tài khoản trước. UnifyPort không có REST API tổng quát để đọc lịch sử tin nhắn và không bảo đảm replay cho payload đã bỏ lỡ. Vì vậy webhook là lớp intake bền vững: tạo POST /v1/webhook-endpoints, bật signing_secret, subscribe message.received hoặc [*], lưu event rồi mới chuyển cho CRM, AI hoặc automation.
Điểm chính
- Đăng ký webhook trước khi nối production messaging account.
- Dùng
signing_secretđể delivery cóX-Device-TimestampvàX-Device-Signature. - Lưu standard event envelope trước các bước chậm như CRM write hoặc AI reasoning.
- Với inbound inbox, bắt đầu bằng
message.receivedvà kiểm tradata.message.direction === "inbound". - Tách event filter, signature verification, retry handling và business routing thành các lớp riêng.
Vì sao webhook phải đi trước
Ở Việt Nam, WhatsApp và Zalo đều có thể là kênh chăm sóc khách hàng. Tin nhắn có thể đến trước khi CRM, AI agent hoặc shared inbox sẵn sàng. Nếu receiver chưa được đăng ký, bạn không nên giả định có thể lấy lại tin nhắn bằng history API sau đó. UnifyPort Quickstart cũng đặt bước đăng ký webhook trước account authorization.
Bài này nối với hai bài đã có: Webhook HMAC replay protection giải thích timestamp, signature và idempotency; UnifyPort webhook event filters giúp chọn subscribed_events hay wildcard. Ở đây chúng ta gom lại thành checklist triển khai đầu tiên.
Bước 1: tạo signed endpoint
API route thật là POST /v1/webhook-endpoints. Nếu chỉ làm inbound inbox, hãy bắt đầu bằng message.received. Nếu endpoint là collector cho mọi public standard event, hãy dùng [*].
curl -X POST https://api.unifyport.ai/v1/webhook-endpoints \
-H "X-Api-Key: $UNIFYPORT_API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"url\": \"$PUBLIC_WEBHOOK_URL\",
\"status\": \"active\",
\"subscribed_events\": [\"message.received\"],
\"signing_secret\": \"$WEBHOOK_SIGNING_SECRET\",
\"retry_policy\": { \"max_attempts\": 3 }
}"
retry_policy.max_attempts là số lần retry sau initial delivery. Giá trị mặc định trong tài liệu là 3, phạm vi hợp lệ là 0 đến 5. Khi triển khai, hãy đối chiếu Create webhook endpoint.
Bước 2: xác minh raw request body
Khi signing được bật, UnifyPort gửi X-Device-Signature. Đây là hex HMAC-SHA256 của chuỗi chính xác sau:
<X-Device-Timestamp>.<raw request body>
Receiver phải verify raw bytes trước khi parse JSON hoặc serialize lại. Node.js cung cấp crypto.createHmac() và crypto.timingSafeEqual() cho mẫu này; tài liệu chính thức nêu rằng buffer dùng với timingSafeEqual() phải có cùng độ dài.
import crypto from 'node:crypto';
import express from 'express';
const app = express();
const signingSecret = process.env.WEBHOOK_SIGNING_SECRET;
app.post('/webhooks/unifyport', express.raw({ type: 'application/json' }), async (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');
const valid = /^[0-9a-f]{64}$/i.test(signature) &&
signature.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
if (!valid) return res.sendStatus(401);
const event = JSON.parse(req.body.toString('utf8'));
await inbox.insertIfAbsent(event.id, event);
return res.sendStatus(202);
});
Trong production, thêm timestamp freshness, durable deduplication theo event ID và acknowledgement có tính đến retry. Xem Webhook delivery and signature verification để biết chi tiết.
Bước 3: lưu standard event envelope
message.received giữ cùng top-level shape trên nhiều provider, vì vậy WhatsApp và Zalo có thể đi vào cùng một intake path.
{
"id": "evt_2f9c1a4b7e",
"type": "message.received",
"provider": "zalo",
"account_id": "acc_8c21d0",
"occurred_at": "2026-06-08T12:34:56Z",
"data": {
"conversation": { "id": "84901234567", "type": "user", "title": "Jordan Lee" },
"sender": { "id": "84901234567", "name": "Jordan Lee", "type": "user" },
"message": {
"id": "msg_10472",
"text": "Đơn hàng đã được gửi chưa?",
"direction": "inbound",
"sent_at": "2026-06-08T12:34:55Z"
}
}
}
Tối thiểu hãy lưu id, type, provider, account_id, occurred_at, data.conversation.id, data.sender.id và data.message.id. Nếu bạn dùng n8n sau edge, n8n WhatsApp AI agent tutorial cho thấy workflow nên nhận event đã verify, không nên là security boundary đầu tiên.
Giới hạn và đánh đổi
Unofficial interface phù hợp khi đội cần nhận inbound messages từ tài khoản thường hoặc tài khoản đang dùng. Nếu bạn cần official certification, tính năng business chính thức hoặc bảo đảm policy từ từng provider, hãy dùng official API của nền tảng đó. Ngoài ra, message.received không đồng nghĩa luôn là inbound; inbox pipeline vẫn phải kiểm tra data.message.direction.
FAQ
Nên subscribe event nào trước?
Với inbound inbox, dùng message.received. Chỉ dùng [*] khi endpoint là full event collector.
HMAC có đủ để chống xử lý trùng không?
Không. HMAC xác minh integrity và shared secret. Bạn vẫn cần timestamp freshness và deduplication bền vững theo event ID.
Có thể trả 2xx trước khi CRM ghi xong không?
Có, nếu event đã được nhận vào durable inbox hoặc queue. CRM, AI và notification nên chạy bất đồng bộ.
Bước tiếp theo
Mở Create webhook endpoint, đăng ký receiver, rồi hoàn thiện xác minh theo webhook delivery guide trước khi nối production account.
Sources
Official sources checked on 2026-08-26:
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.