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ồi | hãng không công bố Stripe không công bố con số nào. Tài liệu chỉ nói phải trả về 2xx trước mọi logic chậm. 20 giây là con số cộng đồng thường dùng |
|---|---|
| Thử lại | Exponential backoff trong tối đa 3 ngày ở live mode. Sandbox: 3 lần gửi trong vài giờ |
| Ngừng gửi | Endpoint bị vô hiệu hóa nếu tiếp tục thất bại, có email báo trước. Sự kiện vẫn nằm trong Events API trong 30 ngày |
| Nguồn | https://docs.stripe.com/webhooks |
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án | HMAC-SHA256 |
|---|---|
| Payload được ký | {timestamp}.{body} |
| Encoding | hex |
| Header | stripe-signature = t=X,v1=hex |
| Dung sai | 5 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");
// stripe-signature: "t=1700000000,v1=abc..." — HMAC-SHA256 over "{t}.{body}"
const pairs = Object.fromEntries(
req.headers["stripe-signature"].split(",").map((p) => p.split("=")),
);
const expected = crypto
.createHmac("sha256", endpointSecret) // whsec_... from the endpoint's page
.update(`${pairs.t}.${rawBody}`)
.digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(pairs.t)) < 300;
const received = Buffer.from(pairs.v1 ?? "");
const wanted = Buffer.from(expected);
// timingSafeEqual throws on a length mismatch, so check that first.
const valid =
fresh && received.length === wanted.length && crypto.timingSafeEqual(received, wanted);Hoặc để SDK làm: stripe.webhooks.constructEvent(rawBody, sig, secret). Hàm này cần raw body, nên hãy gắn express.raw() vào route webhook trước mọi JSON parser.
Những chỗ dễ vấp
Stripe không bao giờ công bố thời hạn phản hồi. Tài liệu chỉ nói phải trả về 2xx trước mọi logic chậm; 20 giây là con số cộng đồng bám theo, và những handler ghi đồng bộ vào một database chậm thì sự kiện nào cũng sát ngưỡng đó.
Sau 3 ngày thất bại, sự kiện lặng lẽ rời khỏi hàng đợi thử lại. Nếu tiếp tục thất bại, endpoint bị vô hiệu hóa, có email báo trước, và các sự kiện vẫn lấy về được qua Events API trong 30 ngày.
Đọc thêm
AnyHook đứng trước các endpoint nhận webhook từ Stripe: 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 Stripe hết hạn sau 5 phút.