Cách HubSpot gửi webhook

Một thời hạn 5 giây cho cả batch tối đa 100 thông báo, và tài liệu ghi rõ eventId không được đảm bảo là duy nhất. Chữ ký v3 và code xác minh Node.

Hành vi gửi

Lấy từ tài liệu của chính hãng, kiểm tra lần cuối 2026-08-06. Hãng không nêu con số nào thì bảng này cũng không ghi.

Thời hạn phản hồi5 giây

Áp dụng cho cả batch, mỗi batch chứa tối đa 100 thông báo

Kiểm tra handler của bạn có phản hồi kịp không

Thử lạiTối đa 10 lần gửi, rải trong 24 giờ
Ngừng gửihãng không công bố
Nguồnhttps://developers.hubspot.com/docs/api-reference/latest/webhooks/guide

Chữ ký

Kiểm chứng bằng một bộ xác minh chạy thật, có bộ test chứng minh, chứ không đọc từ tài liệu. Kiểm chứng lần cuối 2026-08-20.

Thuật toánHMAC-SHA256
Payload được kýPOST{decodedUrl}{body}{timestamp}
Encodingbase64
Headerx-hubspot-signature-v3
Dung sai5 phút

Xác minh

Dùng raw body của request, đúng từng byte, trước mọi bước parse JSON. Mọi cơ chế trên trang này đều hỏng ngay khi framework serialize lại payload.

const crypto = require("node:crypto");

// v3: base64(HMAC-SHA256(secret, "POST" + url + body + timestamp))
const ts = req.headers["x-hubspot-request-timestamp"];
if (Math.abs(Date.now() - Number(ts)) > 300_000) throw new Error("stale");

const expected = crypto
  .createHmac("sha256", clientSecret) // the app's client secret
  .update("POST" + fullUrl + rawBody + ts)
  .digest("base64");
const received = Buffer.from(req.headers["x-hubspot-signature-v3"] ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
  received.length === wanted.length && crypto.timingSafeEqual(received, wanted);

fullUrl là chính xác URL công khai mà HubSpot đã gọi, gồm cả query string, sau khi URL-decode. Nếu đứng sau một proxy viết lại header Host, hãy dựng lại URL từ các header forwarded, nếu không chữ ký sẽ không bao giờ khớp.

Những chỗ dễ vấp

Thời hạn 5 giây tính cho mỗi request, và một request có thể chứa batch tối đa 100 thông báo. Xử lý chúng ngay trong request nghĩa là mỗi thông báo thực chất chỉ có 50 ms.

Tài liệu của HubSpot ghi eventId không được đảm bảo là duy nhất và không hứa hẹn gì về thứ tự, nên thói quen chống trùng lặp theo event id như với Stripe không áp dụng được.

Đọc thêm

AnyHook đứng trước các endpoint nhận webhook từ HubSpot: trả lời sau khoảng 1 giây, tự thử lại theo lịch riêng khi server của bạn ngừng hoạt động, và giữ mọi sự kiện để bạn phát lại được. Đổi một URL, giữ nguyên code; để nhận được các lần thử lại và phát lại, bạn xác minh AnyHook-Signature, vì chữ ký riêng của HubSpot hết hạn sau 5 phút.

Cách hoạt động →