Webhook là một kênh giao sự kiện qua mạng, vì vậy ứng dụng phải giả định sự kiện có thể đến trễ, đến nhiều lần, sai thứ tự hoặc không đến trong lần thử đầu tiên. Một endpoint trả về 200 không đồng nghĩa nghiệp vụ đã hoàn tất; kiến trúc đáng tin cậy cần tách tiếp nhận khỏi xử lý.
Luồng tiếp nhận tối thiểu
- Đọc raw request body và các header cần thiết.
- Xác minh chữ ký cùng giới hạn thời gian để chống replay.
- Kiểm tra event ID trong kho idempotency.
- Lưu event và trạng thái tiếp nhận trong một transaction.
- Đưa công việc vào queue.
- Trả phản hồi 2xx nhanh.
Stripe lưu ý rằng thay đổi raw body trước khi xác minh sẽ làm kiểm tra chữ ký thất bại. Cách ký, header và tolerance khác nhau theo nhà cung cấp, nên luôn dùng SDK hoặc tài liệu chính thức của nguồn phát.
Idempotency chống tác động lặp
Nhà cung cấp có thể gửi lại cùng event khi timeout hoặc nhận lỗi. Tạo unique constraint theo provider + event_id và coi vi phạm unique là sự kiện đã nhận. Chỉ kiểm tra rồi chèn bằng hai thao tác rời rạc có thể tạo race condition.
BEGIN;
INSERT INTO webhook_events(provider, event_id, payload, status)
VALUES (?, ?, ?, 'received')
ON CONFLICT(provider, event_id) DO NOTHING;
COMMIT;
Idempotency cần kéo dài đến tác động nghiệp vụ. Ví dụ, cập nhật đơn hàng và ghi dấu event nên nằm trong cùng transaction; gửi email hoặc gọi dịch vụ khác cần khóa nghiệp vụ hoặc outbox để tránh chạy hai lần.
Phản hồi nhanh, xử lý ở nền
Endpoint không nên tạo hóa đơn, gửi email và đồng bộ CRM trước khi trả lời. Ghi bền event, enqueue rồi trả 2xx giúp tránh timeout và retry không cần thiết. Worker có thể retry độc lập với backoff và đưa lỗi lâu dài vào dead-letter queue.
Chỉ trả 2xx sau khi event đã được lưu bền hoặc được giao chắc chắn cho queue. Trả thành công trước bước này có thể làm mất sự kiện nếu tiến trình dừng.
Không giả định thứ tự sự kiện
Các event có thể được xử lý song song hoặc đến sai thứ tự. Dùng version, timestamp, state machine và điều kiện cập nhật để từ chối trạng thái cũ. Khi nhà cung cấp hỗ trợ, có thể đọc lại resource hiện tại từ API thay vì tin hoàn toàn vào snapshot cũ.
Retry có giới hạn
Chỉ retry lỗi tạm thời như timeout, 429 hoặc 5xx. Lỗi dữ liệu không hợp lệ cần được ghi nhận và chuyển sang hàng chờ xử lý thủ công. Dùng exponential backoff có jitter, giới hạn số lần thử và đặt timeout cho mọi request đi ra.
Bảo mật endpoint
- Chỉ nhận HTTPS và xác minh chữ ký trước khi parse nghiệp vụ.
- Lưu secret trong secret manager và hỗ trợ xoay khóa có giai đoạn chồng lấn.
- Giới hạn kích thước body và loại event đăng ký.
- Không ghi secret hoặc dữ liệu nhạy cảm đầy đủ vào log.
- IP allowlist chỉ là lớp bổ sung vì dải IP có thể thay đổi.
Observability và vận hành
Mỗi event cần correlation ID, thời điểm nhận, số lần thử, trạng thái, lỗi cuối và thời gian xử lý. Dashboard nên hiển thị tỷ lệ xác minh thất bại, độ trễ queue, retry, dead-letter và tuổi của event chưa hoàn tất. Xây công cụ replay có phân quyền, audit log và vẫn đi qua idempotency.
Kịch bản kiểm thử bắt buộc
- Chữ ký sai, timestamp quá cũ và body bị thay đổi.
- Cùng event được gửi đồng thời nhiều lần.
- Worker dừng sau khi cập nhật database nhưng trước khi ack.
- Event đến sai thứ tự.
- Dịch vụ phụ thuộc trả 429, 500 hoặc timeout.
- Replay từ dead-letter queue.
Kết luận
Webhook đáng tin cậy được xây trên giả định giao ít nhất một lần: xác minh nguồn gửi, lưu bền, chống trùng, xử lý bất đồng bộ và quan sát được. Khi các lớp này rõ ràng, retry trở thành cơ chế phục hồi thay vì nguyên nhân tạo dữ liệu trùng.




Chưa có bình luận. Hãy là người đầu tiên chia sẻ ý kiến.