Webhook gửi nhiều lần: làm sao chỉ xử lý một lần?
Thiết kế workflow idempotent để webhook gửi lặp vẫn chỉ tạo một hiệu ứng nghiệp vụ, kể cả khi chạy đồng thời hoặc retry sau lỗi.

Không thể bảo đảm webhook chỉ được gửi đúng một lần. Timeout, mất kết nối, phản hồi chậm, retry tự động hoặc thao tác redelivery đều có thể làm cùng sự kiện đến nhiều lần. Cách đúng là thiết kế workflow idempotent: nhiều delivery của cùng một sự kiện được chấp nhận, nhưng chỉ một execution được quyền tạo hiệu ứng nghiệp vụ như lập đơn hàng, gửi email, ghi nhận thanh toán hoặc cập nhật CRM.
Luồng an toàn là: xác thực chữ ký → tạo idempotency key ổn định → atomic claim trong kho dữ liệu có unique constraint → chỉ bên claim thành công mới xử lý → ghi trạng thái và kết quả → mọi lần gửi lại trả về kết quả an toàn. Đừng dùng một node tìm kiếm rồi một node tạo dữ liệu; hai execution đồng thời có thể cùng thấy “chưa tồn tại” và đều tạo bản ghi.
Nguyên nhân
Webhook trùng là hành vi bình thường của hệ thống phân tán
Retry giúp tránh mất sự kiện, nhưng phía nhận phải chịu trách nhiệm kiểm soát hiệu ứng lặp.
Một nhà cung cấp không nhận được phản hồi 2xx có thể cho rằng delivery thất bại dù workflow đã bắt đầu chạy. Trường hợp khó hơn xảy ra khi tác vụ đích thành công nhưng kết nối bị ngắt trước lúc kết quả được ghi nhận: bên gửi retry, còn bên nhận không biết lần trước đã hoàn tất đến đâu. Stripe công khai lưu ý endpoint đôi khi nhận cùng event nhiều lần; GitHub cũng hỗ trợ redelivery với cùng X-GitHub-Delivery.
Vì vậy mục tiêu production không nên được mô tả là “exactly-once delivery”. Mô hình thực tế là at-least-once delivery, effectively-once business effect. Workflow có thể được kích hoạt nhiều lần, nhưng database, API đích và operation ledger cùng bảo đảm một đơn hàng không được tạo hai lần. Cũng không nên giả định event luôn đến đúng thứ tự; nếu trạng thái mới đã được xử lý, event cũ đến muộn không được phép ghi đè ngược dữ liệu.




