Xác thực webhook
Kiểm chữ ký X-Hubport-Signature của webhook Hubport gửi tới hệ thống của bạn
Khi bạn khai báo Webhook trong Cài đặt > Webhook (ví dụ sự kiện order.add), Hubport gửi POST tới URL
bạn khai mỗi khi sự kiện xảy ra. Mỗi request đều được ký HMAC-SHA256 bằng khoá ký (signing secret) riêng của webhook đó,
để hệ thống của bạn chắc chắn request đến từ Hubport và nội dung không bị sửa.
Đơn hàng từ sàn (Shopee, Lazada, TikTok Shop, Tiki)
Theo chính sách dữ liệu của các sàn, thông tin cá nhân người mua của đơn có source là sàn (11 Shopee, 13 Lazada, 15 TikTok
Shop, 17 Tiki) được che trong nội dung webhook (kể cả khi bạn bấm "Gửi lại webhook"): họ tên dạng N*** V*** A***, số
điện thoại dạng 09xx***678, email dạng a***@..., địa chỉ chỉ còn quận/huyện và tỉnh/thành, shipping_address_line_2,
shipping_instruction, note, tax_name, tax_code, tax_address thành ***. Nội dung có thêm trường
buyer_pii_masked: 1. Mã đơn, sản phẩm, số tiền, trạng thái giữ nguyên. Đơn từ các nguồn khác không đổi.
Khoá ký (signing secret)
- Hệ thống tự sinh khi tạo webhook, dạng
whsec_.... Xem ở form sửa webhook (ô "Khoá ký"), hoặc trườngmeta_data.webhook.secretkhi đọc chi tiết webhook. - Khoá giữ nguyên khi bạn sửa webhook. Bạn không tự đặt được khoá.
- Lộ khoá: tạo khoá mới bằng
POST /v1/hooks/{id}/rotatesecret(quyềnsetting.advanced); khoá cũ hết hiệu lực ngay.
Header gửi kèm
| Header | Mô tả |
|---|---|
X-Hubport-Signature | t=<unix timestamp>,v1=<chữ ký hex> |
CP-Webhook-Event | Mã sự kiện, ví dụ order.add |
CP-Webhook-Time | Thời điểm gửi (unix timestamp), bằng giá trị t ở trên |
CP-Webhook-Retry | Số lần đã gửi trước đó của sự kiện này (0 là lần đầu) |
Content-Type | application/json |
Header tuỳ chỉnh bạn khai trong webhook cũng được gửi kèm (trừ các header hệ thống ở trên, Host, Content-Length).
Cách kiểm chữ ký
- Đọc nguyên văn body request (chuỗi byte, chưa parse JSON).
- Tách
tvàv1từ headerX-Hubport-Signature. - Tính
HMAC-SHA256(secret, "<t>.<body>"), mã hoá hex chữ thường. - So sánh với
v1bằng hàm so sánh hằng thời gian. Khác nhau thì trả401và bỏ qua request. - Nên từ chối request có
tlệch quá 5 phút so với giờ máy chủ của bạn (chống gửi lại).
$body = file_get_contents('php://input');
parse_str(str_replace(',', '&', $_SERVER['HTTP_X_HUBPORT_SIGNATURE'] ?? ''), $sig);
$expected = hash_hmac('sha256', ($sig['t'] ?? '') . '.' . $body, $secret);
if (!hash_equals($expected, $sig['v1'] ?? '') || abs(time() - (int)($sig['t'] ?? 0)) > 300) {
http_response_code(401);
exit;
}// Node.js (Express): dùng express.raw() để lấy body nguyên văn
const crypto = require('crypto');
const sig = Object.fromEntries(req.get('X-Hubport-Signature').split(',').map((p) => p.split('=')));
const expected = crypto.createHmac('sha256', secret).update(`${sig.t}.${req.body}`).digest('hex');
const ok = sig.v1 && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig.v1))
&& Math.abs(Date.now() / 1000 - Number(sig.t)) <= 300;Gửi lại khi lỗi
- Hệ thống coi là thành công khi bạn trả HTTP
2xx. Mã khác, hết thời gian (30 giây) hoặc lỗi kết nối thì gửi lại theo lịch lùi dần (30 giây, 1 phút, 2 phút...), tối đa 12 lần, sau đó bỏ sự kiện. - Hubport không theo redirect (
3xxtính là lỗi). URL phải làhttp/https; trên Hubport SaaS URL phải làhttpstới máy chủ công khai (URL trỏ vào địa chỉ nội bộ bị từ chối khi lưu với lỗierror_webhook_url_not_allowed). - Mỗi sự kiện có thể tới nhiều lần (ví dụ lần trước bạn xử lý xong nhưng trả lỗi): xử lý theo kiểu idempotent.