1. 06:50 06/08/2026
Trong nhiều dự án web/app, webhook xuất hiện khi hệ thống của bạn cần nhận thông báo từ bên thứ ba: cổng thanh toán báo giao dịch thành công, dịch vụ vận chuyển báo đơn đã giao, nền tảng email báo người dùng đã mở thư, hoặc một hệ thống nội bộ báo dữ liệu đã thay đổi. Nhìn qua, webhook chỉ là một endpoint HTTP để nhận request. Nhưng nếu thiết kế qua loa, nó rất dễ gây lỗi dữ liệu, khó debug và tạo lỗ hổng bảo mật.
Webhook khác gì API thông thường?
Với API thông thường, hệ thống của bạn chủ động gọi sang bên khác để lấy hoặc gửi dữ liệu. Với webhook, bên khác chủ động gọi vào hệ thống của bạn khi có sự kiện. Điều này có nghĩa là bạn không kiểm soát hoàn toàn thời điểm request đến, số lần gửi lại, thứ tự sự kiện, hay chất lượng dữ liệu nhận được.
Ví dụ: cổng thanh toán có thể gửi webhook “payment_success” khi khách đã trả tiền. Nếu endpoint của bạn xử lý chậm hoặc trả lỗi, họ có thể gửi lại. Nếu bạn không xử lý trùng lặp, một đơn hàng có thể bị ghi nhận thanh toán nhiều lần hoặc kích hoạt nhiều lần quy trình gửi hàng.
Những nguyên tắc nên áp dụng khi thiết kế webhook
1. Luôn xác thực request
Đừng tin rằng request đến endpoint webhook chắc chắn là từ đối tác thật. Nên dùng cơ chế ký request bằng secret, ví dụ kiểm tra chữ ký HMAC từ payload và timestamp. Nếu bên gửi hỗ trợ IP allowlist thì có thể dùng thêm, nhưng không nên chỉ dựa vào IP vì hạ tầng có thể thay đổi.
Các điểm cần lưu ý:
- Không để secret trong frontend hoặc repository công khai.
- So sánh chữ ký bằng cách an toàn, tránh so sánh chuỗi sơ sài nếu framework có hàm chuyên dụng.
- Kiểm tra timestamp để giảm rủi ro replay request cũ.
2. Trả phản hồi nhanh, xử lý nặng ở phía sau
Webhook endpoint nên làm ít việc nhất có thể: xác thực, kiểm tra dữ liệu cơ bản, ghi nhận sự kiện, rồi trả về thành công. Các việc như cập nhật nhiều bảng, gửi email, gọi API khác, tạo hóa đơn, đồng bộ kho… nên đẩy vào hàng đợi hoặc background job.
Cách này giúp giảm nguy cơ timeout. Nhiều bên gửi webhook sẽ coi request chậm là thất bại và gửi lại, làm hệ thống càng dễ bị quá tải.
Một luồng thực tế có thể là:
- Nhận webhook từ cổng thanh toán.
- Kiểm tra chữ ký.
- Ghi sự kiện vào bảng webhook_events với trạng thái “received”.
- Trả về 200/204.
- Worker xử lý sự kiện và cập nhật đơn hàng.
3. Thiết kế chống xử lý trùng lặp
Webhook có thể bị gửi lại nhiều lần. Đây là hành vi bình thường, không phải lúc nào cũng là lỗi. Vì vậy, hệ thống cần idempotent ở tầng xử lý webhook.
Nếu payload có event_id từ bên gửi, hãy lưu lại và đặt unique constraint. Khi nhận lại cùng event_id, hệ thống có thể bỏ qua hoặc trả thành công mà không xử lý lại.
Nếu không có event_id, có thể tự tạo khóa dựa trên các trường ổn định như provider, event_type, object_id, transaction_id. Tuy nhiên cần cân nhắc kỹ vì mỗi nhà cung cấp có cách gửi sự kiện khác nhau.
4. Đừng giả định thứ tự sự kiện luôn đúng
Một lỗi phổ biến là tin rằng sự kiện sẽ đến đúng thứ tự. Thực tế, do retry, độ trễ mạng hoặc xử lý bất đồng bộ, webhook “order_delivered” có thể đến trước “order_shipped”, hoặc “payment_failed” đến sau “payment_success”.
Thay vì chỉ dựa vào thứ tự nhận webhook, nên kiểm tra trạng thái hiện tại và quy tắc chuyển trạng thái. Ví dụ, đơn hàng đã “paid” thì không nên bị webhook “payment_pending” ghi đè ngược lại. Với các trạng thái quan trọng, có thể cần gọi API của bên cung cấp để xác minh trạng thái mới nhất trước khi cập nhật.
5. Lưu payload gốc để debug
Khi có tranh chấp hoặc lỗi tích hợp, payload gốc rất hữu ích. Nên lưu lại:
- Thời điểm nhận.
- Header quan trọng.
- Payload gốc.
- Kết quả xác thực.
- Trạng thái xử lý.
- Lỗi nếu có.
Tuy nhiên, cần cẩn trọng với dữ liệu nhạy cảm. Nếu payload chứa thông tin cá nhân, token, số thẻ hoặc dữ liệu nhạy cảm khác, hãy che bớt, mã hóa hoặc áp dụng chính sách lưu trữ phù hợp. Đừng biến bảng log webhook thành nơi chứa dữ liệu rủi ro cao.
6. Có cơ chế xử lý lại thủ công
Trong môi trường thực tế, sẽ có lúc webhook xử lý thất bại vì database lỗi, API nội bộ lỗi, thiếu dữ liệu, hoặc code có bug. Một trang quản trị nội bộ để xem và retry webhook thất bại sẽ tiết kiệm rất nhiều thời gian.
Tối thiểu, nên có các trạng thái như:
- received: đã nhận, chưa xử lý.
- processing: đang xử lý.
- processed: xử lý thành công.
- failed: xử lý thất bại.
- ignored: bỏ qua có chủ đích, ví dụ sự kiện không còn phù hợp.
Khi retry, cần đảm bảo logic vẫn chống trùng lặp và không tạo thêm tác dụng phụ ngoài ý muốn.
7. Kiểm thử bằng tình huống xấu
Đừng chỉ test webhook với một payload thành công. Nên kiểm tra thêm:
- Request sai chữ ký.
- Payload thiếu trường.
- Gửi trùng cùng một event nhiều lần.
- Gửi sự kiện sai thứ tự.
- Worker xử lý bị lỗi giữa chừng.
- Bên gửi timeout rồi retry.
- Một lượng lớn webhook đến trong thời gian ngắn.
Những bài test này giúp phát hiện sớm các giả định nguy hiểm trong thiết kế.
Lời khuyên thực tế
Nếu dự án còn nhỏ, bạn không nhất thiết phải xây dựng một hệ thống webhook quá phức tạp ngay từ đầu. Nhưng vẫn nên có ba thứ cơ bản: xác thực request, lưu event_id để chống trùng, và ghi nhận payload để debug. Khi hệ thống lớn hơn, hãy tách xử lý sang background job, bổ sung retry, dashboard nội bộ và cảnh báo lỗi.
Webhook là cầu nối quan trọng giữa các hệ thống. Một endpoint webhook tốt không chỉ “nhận được request”, mà còn phải xử lý được các tình huống thực tế: gửi lại, sai thứ tự, lỗi tạm thời, payload bất thường và yêu cầu truy vết sau này. Làm chắc phần này từ đầu sẽ giúp web/app ổn định hơn rất nhiều khi tích hợp với thanh toán, vận chuyển, email, CRM hoặc các dịch vụ bên ngoài khác.