Explore all courses in this category.
Explore all courses in this category.
Nguyễn Đình Cường
0 comment
09 Nov, 2026
Webhook là cách một hệ thống khác báo cho bạn biết có chuyện vừa xảy ra: thanh toán thành công, đơn hàng đổi trạng thái, có commit mới. Thay vì bạn hỏi liên tục, họ gọi tới địa chỉ của bạn. Vì địa chỉ đó công khai trên mạng, bạn phải xử lý nó cẩn thận.
Bạn đăng ký một URL (ví dụ https://myapp.com/webhooks/payments) với nhà cung cấp. Khi sự kiện xảy ra, họ gửi một request POST chứa dữ liệu JSON tới URL đó. Phía bạn phải trả lời nhanh bằng mã 2xx để xác nhận đã nhận; nếu không, nhà cung cấp coi là thất bại và thử gửi lại.
Ai cũng có thể gửi request giả tới URL của bạn, tuyên bố "khách đã thanh toán". Để tránh bị lừa, hầu hết nhà cung cấp ký nội dung bằng HMAC với một khóa bí mật chung và gửi chữ ký trong header. Bạn tính lại và so sánh:
// Node.js (Express): cần lấy thân request dạng thôconst crypto = require('crypto');app.post('/webhooks/payments', express.raw({ type: 'application/json' }), (req, res) => { const signature = req.get('X-Signature') || ''; const expected = crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(req.body) // Buffer thô, chưa parse JSON .digest('hex'); const ok = signature.length === expected.length && crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected)); if (!ok) return res.status(401).send('chữ ký sai'); const event = JSON.parse(req.body); // ... xử lý res.sendStatus(200);});Hai điểm quan trọng: phải ký trên thân thô của request (parse rồi chuỗi hóa lại sẽ làm sai chữ ký), và so sánh bằng hàm thời gian không đổi như timingSafeEqual. Chi tiết tên header và cách ký khác nhau theo từng nhà cung cấp, hãy đọc tài liệu của họ. Một số còn kèm mốc thời gian trong chữ ký để bạn từ chối request quá cũ, chống tấn công phát lại.
Nhà cung cấp thường chờ vài giây; nếu bạn xử lý lâu họ sẽ coi là timeout và gửi lại. Cách tốt nhất: kiểm tra chữ ký, lưu sự kiện thô vào database hoặc hàng đợi, trả 200 ngay, rồi để worker xử lý nghiệp vụ ở nền. Cách này vừa nhanh vừa không mất sự kiện.
Hầu hết hệ thống webhook giao "ít nhất một lần", nên cùng một sự kiện có thể tới hai ba lần. Nếu mỗi lần bạn đều cộng tiền hay gửi email, khách sẽ bị tính nhiều lần. Giải pháp: lưu id sự kiện và bỏ qua nếu đã xử lý.
CREATE TABLE webhook_events ( event_id VARCHAR(100) PRIMARY KEY, received_at DATETIME NOT NULL, processed_at DATETIME NULL);try { DB::table('webhook_events')->insert([ 'event_id' => $event['id'], 'received_at' => now(), ]);} catch (UniqueConstraintViolationException $e) { return response('đã xử lý', 200); // sự kiện trùng}Khóa chính trên event_id khiến lần chèn thứ hai thất bại, an toàn ngay cả khi hai bản sao tới cùng lúc.
Khi phát triển ở máy cục bộ, bạn có thể dùng công cụ tạo đường hầm để nhận webhook từ internet về localhost, hoặc dùng chức năng "gửi sự kiện thử" của nhà cung cấp. Hãy viết test tự động cho ba tình huống: chữ ký đúng, chữ ký sai và sự kiện trùng lặp.
Nhớ bốn bước: xác thực chữ ký trên thân thô, trả lời nhanh và xử lý nền, bảo đảm idempotent, và chuẩn bị cho thứ tự lộn xộn cùng đối soát định kỳ. Làm đủ những điều này, tích hợp thanh toán hay dịch vụ bên ngoài của bạn sẽ đáng tin cậy hơn hẳn.
Khi cần một lộ trình học có hệ thống, bạn có thể tham khảo khóa học trên codebem.com.
Nguyễn Đình Cường
0 comment