1. 13:31 24/08/2026
Webhook là cách một hệ thống chủ động gửi thông báo HTTP đến hệ thống khác khi có sự kiện xảy ra. Ví dụ, sau khi thanh toán thành công, nền tảng bán hàng gửi sự kiện cho hệ thống kho để trừ tồn, đồng thời thông báo cho dịch vụ gửi email. Cách này giúp các hệ thống tích hợp gần thời gian thực mà không phải liên tục gọi API để hỏi trạng thái.
Tuy nhiên, webhook dễ thất bại nếu chỉ đơn giản gửi một request POST rồi bỏ qua kết quả. Mạng có thể chập chờn, máy nhận có thể tạm thời quá tải, hoặc request đã được xử lý nhưng phản hồi bị mất. Một thiết kế đáng tin cậy nên xem việc giao nhận webhook là quá trình có thể lặp lại, thay vì giả định mỗi sự kiện chỉ đến đúng một lần.
1. Đặt mã sự kiện duy nhất
Mỗi webhook nên có event_id không trùng, kèm event_type, occurred_at và dữ liệu cần thiết. Bên nhận lưu event_id đã xử lý để nhận biết các lần gửi lại. Chẳng hạn, sự kiện payment.completed có thể được gửi ba lần, nhưng hệ thống kho chỉ được tạo một giao dịch xuất hàng.
Không nên dùng thời điểm nhận request làm mã duy nhất vì hai lần gửi lại có thể có thời gian khác nhau. Mã này nên được tạo từ phía phát sinh sự kiện và tồn tại ổn định trong toàn bộ vòng đời giao nhận.
2. Xác thực chữ ký trước khi xử lý
Bên gửi có thể tạo chữ ký HMAC từ raw body, timestamp và một secret dùng chung. Bên nhận kiểm tra chữ ký bằng chính raw body nhận được, sau đó từ chối request có chữ ký sai hoặc timestamp quá cũ. Cần so sánh chữ ký theo cách chống timing attack nếu thư viện hỗ trợ.
Secret không nên đặt trực tiếp trong mã nguồn. Hãy lưu trong secret manager hoặc biến môi trường, có quy trình thay khóa và cho phép tồn tại ngắn hạn hai khóa trong giai đoạn chuyển đổi.
3. Phản hồi nhanh, xử lý sau
Endpoint webhook không nên thực hiện toàn bộ công việc nặng trước khi trả lời. Một luồng phù hợp là:
- Kiểm tra chữ ký, cấu trúc và thời gian của request.
- Ghi sự kiện vào cơ sở dữ liệu hoặc hàng đợi bền vững.
- Trả về mã thành công trong thời gian ngắn.
- Để worker xử lý nghiệp vụ phía sau.
Nếu trả về lỗi khi chưa ghi được sự kiện, bên gửi có thể thử lại. Ngược lại, không nên trả thành công trước khi dữ liệu đã được lưu an toàn, vì khi đó sự cố tiến trình có thể làm mất sự kiện.
4. Retry có kiểm soát
Bên gửi nên thử lại khi gặp lỗi mạng, timeout hoặc mã phản hồi thuộc nhóm lỗi tạm thời. Khoảng chờ nên tăng dần, có một độ lệch ngẫu nhiên nhỏ để tránh nhiều request dồn cùng lúc. Cần đặt số lần thử tối đa và đưa sự kiện thất bại lâu dài vào dead-letter queue để kiểm tra thủ công.
Không phải lỗi nào cũng nên retry. Request sai chữ ký, sai định dạng hoặc tài nguyên không tồn tại thường là lỗi cố định; retry liên tục chỉ làm tăng tải và che khuất nguyên nhân thật.
5. Xử lý thứ tự và trạng thái
Webhook có thể đến trễ hoặc không đúng thứ tự. Nếu đơn hàng có các sự kiện order.created, order.paid và order.cancelled, hệ thống nhận không nên chỉ dựa vào thứ tự đến. Có thể lưu occurred_at, số phiên bản trạng thái hoặc đọc lại trạng thái hiện tại từ hệ thống nguồn trước khi thực hiện hành động quan trọng.
Trong thực tế, hãy xác định rõ sự kiện nào là lệnh cần thực thi một lần và sự kiện nào chỉ là thông báo trạng thái. Với sự kiện có tác động tài chính hoặc tồn kho, nên kiểm tra thêm trạng thái hiện tại để tránh cập nhật ngược do một webhook cũ đến muộn.
6. Có công cụ vận hành
Một màn hình quản trị tối thiểu nên cho biết event_id, loại sự kiện, thời điểm gửi, số lần thử, mã phản hồi cuối cùng và nội dung lỗi đã được che dữ liệu nhạy cảm. Cho phép nhân viên gửi lại một sự kiện sau khi sửa lỗi, nhưng phải giữ nguyên event_id để cơ chế chống trùng vẫn hoạt động.
Trước khi đưa vào sản xuất, hãy thử các tình huống mất mạng, timeout sau khi bên nhận đã xử lý, nhận trùng sự kiện, sai chữ ký, đến sai thứ tự và queue bị đầy. Webhook tốt không phải là webhook chưa từng lỗi, mà là webhook có thể phát hiện, thử lại và phục hồi lỗi mà không tạo ra hậu quả lặp.