Tích hợp webhook thanh toán: nhận tiền vào ACB trong 5 phút
Video hướng dẫn
Xem transcript video (9 phần)
Webhook trong năm phút
Khách đã chuyển khoản nhưng đơn vẫn nằm chờ vì hệ thống của bạn chưa nhận được báo có. Trong khoảng năm phút, mình sẽ đi trọn luồng webhook: khai báo URL, nhận đúng bảy trường JSON, kiểm chữ ký, trả mã thành công và chống xử lý trùng. Cuối video, bạn còn biết cách thử endpoint và đọc lỗi trước khi đưa lên môi trường thật.
Luồng tiền đến endpoint
Luồng bắt đầu khi tiền vào tài khoản ACB qua tài khoản ảo, VietQR hoặc chuyển khoản thường. ACB báo giao dịch cho MONA Pay. Hệ thống lưu giao dịch, tìm các cấu hình webhook phù hợp rồi gửi HTTP POST đến URL của bạn. Cấu hình “mọi tài khoản” nhận mọi giao dịch. Cấu hình gắn một tài khoản ảo chỉ nhận tiền vào đúng tài khoản đó. Máy chủ có tối đa mười giây để phản hồi.
Khai báo URL webhook
Trong dashboard, vào mục Webhooks rồi bấm Thêm webhook. Đặt Tên dễ nhớ, chẳng hạn Web bán hàng. Trường URL phải là địa chỉ HTTPS công khai. Kiểu xác thực nên chọn HMAC_SHA256, nhập Secret riêng. Định dạng gửi mặc định là application/json. Ở Tài khoản áp dụng, chọn mọi tài khoản hoặc một tài khoản ảo cụ thể. Nếu đang làm tại máy, dùng đường hầm HTTPS vì hệ thống không gọi được localhost.
Đọc bảy trường payload
Payload mặc định có bảy trường. amount là số tiền VND dạng số nguyên. description là nội dung chuyển khoản. transfer_date là thời điểm giao dịch. transaction_code là mã giao dịch ngân hàng và cũng là khóa chống trùng. account_number là số tài khoản thật hoặc tài khoản ảo nhận tiền. bank_name là tên ngân hàng. type hiện mang giá trị income, nghĩa là tiền vào. Endpoint phải bỏ qua trường lạ được thêm sau này.
Kiểm chữ ký, trả sớm
Ở endpoint, thứ tự xử lý rất quan trọng. Đọc raw body nguyên bản trước khi bộ phân tích JSON thay đổi khoảng trắng hoặc thứ tự khóa. Lấy X-Mona-Timestamp và X-Mona-Signature, tính lại HMAC-SHA256 bằng Secret, rồi so chữ ký bằng hàm thời gian cố định. Từ chối timestamp lệch quá năm phút. Chữ ký đúng thì trả HTTP hai trăm ngay; cập nhật đơn, gửi email hoặc gọi dịch vụ khác hãy đẩy sang hàng đợi.
Chống xử lý trùng
Sau khi trả lời, giải mã JSON và chèn giao dịch vào bảng của bạn. Cột transaction_code phải có ràng buộc UNIQUE. Với MySQL có thể dùng INSERT IGNORE; với PostgreSQL dùng ON CONFLICT DO NOTHING. Nếu chèn không thành công, đây là gói đã xử lý nên dừng. Khi là gói mới, khớp đơn bằng account_number nếu mỗi đơn có một tài khoản ảo, hoặc bằng description, rồi mới đổi trạng thái đơn.
Kiểm tra trước production
Trước khi chạy thật, bấm Gửi thử và mở tab Lịch sử gửi. Kết quả hợp lệ là HTTP hai trăm, hai trăm lẻ một hoặc hai trăm lẻ hai, thời gian phản hồi dưới một giây. Endpoint phải dùng HTTPS có chứng chỉ hợp lệ, không chuyển hướng và tường lửa phải cho phép IP 103.168.55.14. Cuối cùng, chuyển một khoản nhỏ vào tài khoản ảo để kiểm tra trọn luồng từ ngân hàng đến đơn hàng.
Sửa lỗi webhook
Nếu chữ ký sai dù Secret đúng, gần như raw body đã bị framework phân tích rồi tạo lại. Nếu thấy TIMEOUT, endpoint đang làm việc nặng trước khi trả lời; hãy trả hai trăm trước. HTTP_4XX với bốn trăm lẻ bốn thường là sai đường dẫn, còn bốn trăm lẻ năm là route không nhận POST. Không nhận gì thì kiểm tra cấu hình có đang gắn nhầm tài khoản ảo và xem Lịch sử gửi.
Mở tài liệu và thử
Mẫu PHP, Node, lệnh thử cURL và toàn bộ danh sách kiểm tra nằm tại monapay.vn/docs/webhooks/tich-hop-webhook. Bạn có thể đăng ký tài khoản tại my.monapay.vn, tạo cấu hình thử rồi đối chiếu từng bước trong tài liệu. Nếu endpoint vẫn chưa nhận đúng payload, gọi tổng đài một chín không không, sáu ba sáu, sáu bốn tám để đội MONA hỗ trợ kiểm tra.
Webhook MONA Pay là một request HTTP POST gửi tới URL của bạn mỗi khi có tiền vào tài khoản ACB đã nối. Để tích hợp, bạn khai báo URL trong dashboard hoặc qua API, viết một endpoint nhận JSON, kiểm chữ ký, trả HTTP 200 trong 10 giây rồi xử lý đơn hàng. Phần này mất khoảng 30 dòng code, có mẫu PHP và Node dán vào dùng ngay.
Luồng một webhook
- Tiền vào tài khoản ACB (qua VA, VietQR hoặc chuyển khoản thường).
- ACB báo giao dịch cho MONA Pay, MONA Pay lưu vào dashboard.
- MONA Pay tìm các cấu hình webhook khớp: cấu hình “mọi tài khoản” luôn được gửi, cấu hình gắn một VA chỉ gửi khi giao dịch thuộc VA đó.
- Với mỗi cấu hình, MONA Pay POST payload tới URL, kèm chữ ký nếu bật HMAC, chờ tối đa 10 giây.
- Máy chủ của bạn trả HTTP 200, 201 hoặc 202: ghi log thành công. Mã khác hoặc quá 10 giây: ghi log thất bại kèm nhãn lỗi, bạn gửi lại từ dashboard.
Khai báo URL webhook
Trong dashboard
Vào mục Webhooks, bấm Thêm webhook, điền:
| Trường | Ý nghĩa |
|---|---|
| Tên | Đặt cho dễ nhớ, ví dụ “Web bán hàng”, “Phần mềm học phí” |
| URL | Địa chỉ HTTPS nhận webhook trên máy chủ của bạn |
| Kiểu xác thực | HMAC_SHA256 (khuyên dùng), API_KEY hoặc NONE |
| Secret | Chuỗi bí mật dùng để ký (HMAC) hoặc gửi kèm header (API key) |
| Định dạng gửi | application/json (mặc định), application/x-www-form-urlencoded, multipart/form-data |
| Tài khoản áp dụng | Mọi tài khoản, hoặc chọn một VA cụ thể |
Qua API
Các lệnh liên quan: GET /api/v1/client-webhooks xem danh sách, PUT /api/v1/client-webhooks/{config_id} sửa, DELETE /api/v1/client-webhooks/{config_id} xoá, POST /api/v1/client-webhooks/test gửi gói mẫu. Chi tiết tham số ở Cấu hình webhook qua API.
Chạy webhook trên localhost
Webhook cần một URL HTTPS công khai, MONA Pay không thể gọi trực tiếp vào localhost.
Dán URL HTTPS do tunnel cấp vào webhook_url; bạn cũng có thể dùng ngrok.
Nếu chưa có URL công khai, hãy hỏi lại GET /api/v1/checkouts/{checkout_id} mỗi vài giây trong lúc thử.
Trang Địa chỉ IP chỉ áp dụng khi đưa hệ thống lên production.
Payload MONA Pay gửi
transaction_code giữ nguyên qua mọi lần gửi lại, dùng làm khoá chống trùng. account_number là số VA hoặc số tài khoản nhận tiền. Từng trường giải thích ở Định dạng payload.
Viết endpoint nhận webhook
Endpoint tốt làm đúng 3 việc theo thứ tự: kiểm chữ ký, trả 200 ngay, xử lý sau. Việc nặng (cập nhật đơn, gửi email, gọi API khác) đẩy vào hàng đợi hoặc làm sau khi đã trả lời, để không bao giờ chạm mốc 10 giây.
Chống trùng giao dịch
Cùng một giao dịch có thể tới endpoint của bạn nhiều lần: gửi lại thủ công từ dashboard, hai cấu hình webhook cùng trỏ một URL, hoặc máy chủ trả lỗi rồi được gửi lại. Cách xử lý chuẩn là tạo cột transaction_code có ràng buộc UNIQUE trong bảng giao dịch và dùng INSERT IGNORE (MySQL) hoặc ON CONFLICT DO NOTHING (PostgreSQL). Gói nào chèn không vào là gói đã xử lý, bỏ qua. Đừng dựa vào cặp số tiền + thời gian, hai khách chuyển cùng số tiền trong cùng phút là chuyện bình thường.
Kiểm tra trước khi lên production
- Endpoint chạy HTTPS với chứng chỉ hợp lệ. Chứng chỉ tự ký hoặc hết hạn sẽ ra nhãn lỗi
SSL. - Tường lửa cho phép IP
103.168.55.14(xem Địa chỉ IP). - Không có redirect ở URL webhook (ví dụ từ
httpsanghttpshoặc thêm dấu/cuối). Redirect ra nhãnHTTP_3XXvà tính là thất bại. - Bấm Gửi thử trong dashboard, xem Lịch sử gửi có mã 200 và thời gian phản hồi dưới 1 giây.
- Chuyển thử một khoản nhỏ vào VA để chạy luồng thật.
Lỗi thường gặp
Webhook về nhưng chữ ký không khớp. Framework (Laravel, Express, Next.js) đã parse JSON rồi mới lấy body để ký. Phải lấy raw body nguyên bản. Xem cách lấy raw body từng framework ở Bảo mật webhook.
Nhãn TIMEOUT dù endpoint chạy được. Endpoint xử lý đơn xong mới trả lời, vượt 10 giây khi hệ thống bận. Trả 200 trước, xử lý sau.
Nhãn HTTP_4XX với mã 404 hoặc 405. Sai đường dẫn, hoặc route chỉ nhận GET. Webhook luôn là POST.
Nhận được 2 lần cùng một giao dịch. Bình thường. Chống trùng bằng transaction_code như mục trên.
Không nhận được gì dù tiền đã vào. Cấu hình webhook đang gắn một VA nhưng khách chuyển vào tài khoản chính hoặc VA khác. Tạo thêm một cấu hình “mọi tài khoản” để không sót, hoặc kiểm tra Lịch sử gửi trong dashboard xem MONA Pay có gửi không.