Webhook ngân hàng là một cơ chế HTTP Callback cho phép hệ thống phần mềm của bạn nhận dữ liệu giao dịch theo thời gian thực. Khác với mô hình Polling (bạn phải liên tục gọi API để hỏi xem có giao dịch mới không), với Webhook, ngân hàng (hoặc nền tảng trung gian) sẽ chủ động gửi một HTTP POST request đến URL (Endpoint) mà bạn đã đăng ký ngay khi có sự biến động số dư.

Tại sao Webhook lại vượt trội hơn API Polling?

Trong các nghiệp vụ yêu cầu tính tức thời như mở khóa khóa học online, nạp tiền vào ví điện tử, hay thanh toán tại quầy POS, việc chậm trễ dù chỉ 1 phút cũng có thể làm khách hàng khó chịu. Webhook giải quyết triệt để vấn đề này nhờ cơ chế Push (đẩy dữ liệu). Ngoài ra, nó còn giúp giảm tải đáng kể cho server của bạn vì bạn không phải thực hiện các request vô ích (khi không có giao dịch mới).

Bốn nguyên tắc vàng khi thiết kế Endpoint nhận Webhook

1. Bảo mật: Xác thực trước khi đọc dữ liệu

Endpoint của bạn thường phải là public để ngân hàng có thể truy cập được. Điều này đồng nghĩa với việc ai cũng có thể giả mạo ngân hàng để gửi một request "chuyển khoản thành công" ảo đến hệ thống của bạn. Để ngăn chặn, bạn BẮT BUỘC phải thực hiện xác thực (Authentication):

  • Kiểm tra HMAC Signature: Mỗi Webhook gửi đi đều đi kèm một chữ ký điện tử (Signature) trong HTTP Header (ví dụ: X-Webhook-Signature). Bạn dùng Secret Key của mình để mã hóa lại dữ liệu nhận được và so sánh với Signature này. Chỉ khi khớp hoàn toàn, bạn mới được phép xử lý.
  • Sử dụng hàm Constant-time: Khi so sánh chuỗi mã hóa, hãy dùng các hàm như hash_equals() trong PHP để chống lại tấn công Timing Attack.
  • Kiểm tra IP (IP Whitelisting): Chỉ cho phép nhận Webhook từ các dải IP được ngân hàng hoặc nền tảng công bố chính thức.

2. Tính lũy đẳng (Idempotency): Chống xử lý trùng lặp

Do tính chất của mạng internet (Network Jitter, Timeout), ngân hàng có thể gửi cùng một thông báo giao dịch nhiều lần nếu họ không nhận được phản hồi thành công (HTTP 200) từ server của bạn đủ nhanh. Nếu bạn cộng tiền mỗi lần nhận được request, khách hàng sẽ được cộng tiền nhiều lần cho cùng một giao dịch.

Giải pháp: Sử dụng transactionID (Mã tham chiếu giao dịch của ngân hàng) làm khóa chính (Primary Key/Unique Index) trong cơ sở dữ liệu. Trước khi xử lý, hãy truy vấn xem transactionID này đã tồn tại chưa. Nếu đã có, hãy bỏ qua việc cộng tiền nhưng vẫn trả về mã 200 OK cho ngân hàng.

3. Lưu trước, xử lý sau (Asynchronous Processing)

Endpoint nhận Webhook của bạn cần phản hồi (trả về HTTP 200) càng nhanh càng tốt (thường dưới 3 giây). Nếu hệ thống của bạn tốn quá nhiều thời gian để gửi Email xác nhận, gọi API bên thứ ba, hoặc sinh hóa đơn PDF, ngân hàng sẽ coi như request bị Timeout và đánh dấu là thất bại (và sẽ thử gửi lại).

Giải pháp tốt nhất:

  • Bước 1: Xác thực Signature.
  • Bước 2: Lưu thô toàn bộ JSON payload vào bảng webhook_logs.
  • Bước 3: Trả ngay mã HTTP 200 OK.
  • Bước 4: Sử dụng Background Job (Queue/Worker) hoặc Cronjob để đọc dữ liệu từ bảng webhook_logs và thực hiện các nghiệp vụ nặng phía sau.

4. Cơ chế đối soát dự phòng (Fallback & Retry)

Không có hệ thống nào hoạt động hoàn hảo 100%. Server của bạn có thể bị sập (Downtime), hoặc mạng bị gián đoạn đúng lúc ngân hàng gửi Webhook. Để không bị mất dữ liệu:

  • Cơ chế Retry: Đảm bảo rằng đối tác cung cấp Webhook có cơ chế gửi lại (Ví dụ: thử lại sau 1 phút, 5 phút, 30 phút nếu server bạn trả về mã 5xx hoặc Timeout).
  • Đối soát chéo bằng API: Ngoài Webhook, bạn nên thiết lập một Cronjob chạy vào cuối ngày (hoặc mỗi giờ) để gọi API lấy toàn bộ danh sách giao dịch và đối chiếu lại với những gì Webhook đã nhận. Quá trình này giúp "vớt" lại các giao dịch bị rớt.

Phản hồi đúng chuẩn cho Webhook

Một HTTP Response hợp lệ cho Webhook nên có định dạng đơn giản và mã trạng thái chuẩn xác:

HTTP/1.1 200 OK
Content-Type: application/json

{"success": true, "message": "Webhook received successfully"}

Lưu ý: Đừng trả về mã 400 hoặc 500 nếu đó là lỗi nghiệp vụ (ví dụ: Không tìm thấy mã đơn hàng). Chỉ trả mã lỗi khi có sự cố hệ thống không thể lưu trữ dữ liệu, để ngân hàng biết đường gửi lại.

Checklist trước khi Golive

  • Endpoint phải chạy trên HTTPS (SSL/TLS).
  • Secret Key nằm trong biến môi trường (.env), tuyệt đối không hardcode.
  • Giới hạn kích thước request body (chống tấn công DDOS payload lớn).
  • Thiết lập cảnh báo (Alert) qua Telegram/Slack khi Webhook rớt liên tục.

Xem payload và code nhận webhook

Tài liệu có sẵn ví dụ cho PHP, Node.js, Python và cơ chế xác thực an toàn.

Tài liệu webhook