Webhook giao dịch ngân hàng Pay2S cho phép hệ thống đối tác nhận thông báo tiền vào hoặc tiền ra từ tài khoản đã liên kết. Đây là nền tảng để cập nhật đơn hàng, đối soát chuyển khoản, kích hoạt quy trình hóa đơn hoặc hiển thị biến động số dư mà không phải liên tục gọi API lịch sử. Tuy nhiên, nhận được một POST không đồng nghĩa giao dịch đã được doanh nghiệp xác nhận.
Điểm khó của receiver không nằm ở việc mở một URL nhận POST, mà ở chỗ phân biệt tiền IN, OUT và phạm vi ALL, hiểu các field tùy chọn như paymentCode hoặc extraData, rồi quyết định giao dịch nào được gắn với đơn. Hướng dẫn trên hoadontudong.com.vn đi từ cấu hình webhook đến khôi phục một lần gửi lỗi để tránh ghi nhận thanh toán hai lần hoặc bỏ sót khoản thu.
Tạo webhook cho đúng tài khoản
Backend trước hết phải lấy user_bank_id từ GET /v1/banks. Đây là ID bản ghi tài khoản ngân hàng trên Pay2S, không phải accountNumber và không phải một mã tự sinh. POST /v1/webhooks nhận user_bank_id, webhook_url và type. webhook_url cần là endpoint HTTPS do đối tác kiểm soát. type IN chỉ nhận tiền vào, OUT chỉ nhận tiền ra, còn ALL nhận cả hai loại.
Response tạo webhook trả token riêng cùng thông tin cấu hình. Token này được Pay2S gửi trong Authorization: Bearer khi gọi endpoint. Không dùng Bearer Token gọi API làm webhook token và không công khai token trong frontend. Sau khi tạo, gọi GET /v1/webhooks để kiểm tra URL, loại giao dịch, trạng thái, tài khoản và flag cấu hình.
Cấu trúc payload giao dịch

Payload có mảng transactions. Mỗi phần tử có thể gồm id, gateway, transactionDate, transactionNumber, accountNumber, content, transferType, transferAmount và checksum. Một số giao dịch có thêm paymentCode, billNumber, partnerRefId, TID, teller, sequence hoặc extraData. Không được yêu cầu tất cả trường đều có mặt, vì dữ liệu phụ thuộc ngân hàng, loại giao dịch và cấu hình.
Số tiền cần được đọc theo kiểu số mà API trả về và đối chiếu với đơn hàng theo quy tắc doanh nghiệp. Số tài khoản, transactionNumber, paymentCode, TID và các mã có số 0 đầu nên giữ dạng chuỗi. gateway là tên hoặc mã nguồn ngân hàng, không nên dùng nó để tự suy ra reciprocalBankCode. Khi field tùy chọn vắng mặt, coi là null thay vì làm hỏng toàn bộ payload.
paymentCode, extraData và người chuyển
paymentCode nằm trong từng transactions[i], không nằm ngoài mảng. Nó có thể xuất hiện khi webhook có yêu cầu mã thanh toán, đã chọn mẫu và nội dung giao dịch khớp mẫu. paymentCode là mã đối chiếu do worker trích từ nội dung, không phải transactionNumber của ngân hàng. Backend có thể dùng nó để tìm đơn nhưng vẫn nên kiểm tra tài khoản nhận và số tiền.
extraData là object tùy chọn chứa remitterName, remitterAccountNumber, issuerBankName và reciprocalBankCode khi cấu hình bật và ngân hàng cung cấp. Nếu thiếu, worker có thể trả N/A; không coi N/A là dữ liệu thật. Không nhầm extraData này với chuỗi Base64 của API tạo thanh toán. Khi lưu thông tin người chuyển, cần tuân thủ chính sách dữ liệu và không dùng nó để thay thế mã đối chiếu chính.
Tài khoản ảo và mã tham chiếu
Với giao dịch có tài khoản ảo, tài liệu mô tả accountNumber có thể được dựng bằng tiền tố 963869 cộng với vaNumber, còn TID giữ vaNumber gốc khi đi cùng partnerRefId. Đây là quy tắc hiện tại của worker, không phải lý do để backend tự thêm tiền tố lần nữa. Hãy dùng giá trị API trả về, lưu cả trường gốc khi có và giữ đúng kiểu chuỗi.
partnerRefId, billNumber và TID có thể giúp liên kết giao dịch với hệ thống đối tác, nhưng không phải giao dịch nào cũng có đủ. Thiết kế database nên để nullable và có chỉ mục khi trường xuất hiện. Khi cần tìm đơn, ưu tiên mã tham chiếu đã được tạo có chủ đích; nội dung chuyển khoản chỉ là tín hiệu phụ nếu khách nhập sai, thiếu hoặc trùng.
Xác thực, ACK và chống trùng
Receiver kiểm tra Authorization Bearer bằng token của webhook, kiểm tra Content-Type, parse payload sau khi đã xác thực và lưu dữ liệu bền vững trước khi trả thành công. Nếu có checksum hoặc id giao dịch, tạo unique key để request gửi lại không chạy nghiệp vụ lần hai. Khi một giao dịch đã cập nhật đơn, request lặp phải trả 2xx nhưng bỏ qua tác động trùng.
Không nên xử lý mọi việc đồng bộ trước khi ACK. Nếu endpoint phải gọi nhiều dịch vụ, có thể timeout khiến Pay2S gửi lại dù giao dịch đã ghi nhận một phần. Cách an toàn là lưu raw payload và một bản ghi nhận, đẩy job vào queue rồi trả response thành công. Nếu database hoặc hàng đợi không ghi được, trả lỗi để có cơ hội gửi lại.
Quản lý webhook, lịch sử và gửi lại
PUT /v1/webhooks/{id} cập nhật URL hoặc type. PATCH /v1/webhooks/{id}/toggle bật tắt mà không xóa cấu hình. DELETE xóa webhook; trước khi xóa Production cần kiểm tra ảnh hưởng đến các tài khoản hoặc cửa hàng đang nhận tiền. GET /v1/webhooks/history giúp xem endpoint, payload, response, status_code, trạng thái và thời điểm Pay2S đã gửi.
Nếu response khác 200 hoặc request lỗi, POST /v1/webhooks/history/{history_id}/resend cho phép yêu cầu gửi lại. Gửi lại không phải tạo giao dịch mới; receiver vẫn cần chống trùng. Màn hình vận hành nên cho biết lần gửi đầu, lần gửi lại và bản ghi giao dịch đã xử lý. Analytics API thanh toán ngân hàng Pay2S có thể bổ sung góc nhìn về lỗi và thời gian gọi.
Nối webhook với đơn hàng và hóa đơn

Một luồng thanh toán thực tế thường nhận transaction, xác định tài khoản nhận, tìm mã thanh toán, kiểm tra số tiền, kiểm tra trạng thái đơn chưa hoàn tất, ghi nhận thanh toán và phát sự kiện nội bộ. Nếu doanh nghiệp có hóa đơn tự động, chỉ sau khi giao dịch khớp mới chuyển đơn sang quy trình lập hóa đơn. Không để webhook tự ý phát hành khi chưa kiểm tra mã đơn, số tiền và trạng thái xử lý trước đó.
Khi giao dịch không tìm thấy đơn, số tiền lệch hoặc tài khoản không khớp, đưa vào hàng chờ đối soát. Giữ payload làm bằng chứng kỹ thuật và lưu quyết định xử lý riêng. Với HĐĐT, hướng dẫn đối soát hóa đơn điện tử giúp kiểm tra chênh lệch, nhưng trạng thái nhận tiền và phát hành vẫn phải được quản lý độc lập.
Góc nhìn triển khai thực tế
Dữ liệu giao dịch cần được chuẩn hóa nhưng không được làm mất bản gốc. Hãy lưu payload nguyên bản, checksum hoặc id do Pay2S cung cấp, rồi tạo bản ghi chuẩn gồm số tiền, loại IN hoặc OUT, thời điểm, tài khoản, nội dung và mã tham chiếu. Số tiền nên dùng kiểu số chính xác hoặc đơn vị nhỏ nhất phù hợp, không dùng floating point trong phép đối soát. Nội dung chuyển khoản có thể chứa ký tự đặc biệt và thay đổi cách viết hoa, vì vậy quy tắc tìm mã đơn nên có phiên bản, log kết quả và hàng chờ cho những trường hợp không chắc chắn.
Chống trùng cần diễn ra ở cả webhook receiver và nghiệp vụ. Unique key theo id hoặc checksum ngăn tạo hai transaction; nhưng cùng một giao dịch có thể được gửi lại với metadata khác, nên cần lưu lịch sử nhận và quyết định cập nhật minh bạch. Trước khi xác nhận đơn, kiểm tra tài khoản nhận, hướng tiền, số tiền, currency nếu có, khoảng thời gian và partnerRefId hoặc billNumber. Nếu chỉ khớp nội dung nhưng sai số tiền, không tự động hoàn tất. Hãy đưa record vào màn hình chờ đối soát để người có quyền xử lý.
Trong một ca webhook bị chậm, hãy so ba dấu mốc: thời gian giao dịch ở nguồn, lúc Pay2S gửi và lúc receiver lưu thành công. Khoảng chậm ở mỗi đoạn dẫn tới cách xử lý khác nhau; gửi lại chỉ hữu ích khi xác định được lần nhận còn thiếu. Với tài khoản ảo, đối chiếu accountNumber nguyên bản trước khi nghi ngờ mapping. Một tiền tố bị thêm hai lần ở adapter có thể làm mọi lần resend tiếp tục không tìm thấy tài khoản dù dữ liệu nguồn hoàn toàn giống nhau.
Kiểm soát chất lượng và vận hành
Bộ kiểm thử vận hành nên dùng payload đã che dữ liệu nhưng giữ đúng kiểu của transactionDate, transferType, transferAmount và accountNumber. Chạy lại cùng payload sau khi sửa parser để so kết quả chuẩn hóa với bản đã lưu, nhưng tắt tác động giao hàng hoặc phát hành hóa đơn trong lượt thử. Nếu một bản ghi đã được xử lý, việc sửa cách đọc nội dung không được âm thầm tính nó thành khoản thu mới. Lưu phiên bản parser sẽ giúp truy ra thay đổi nào tạo chênh lệch.
Với giao dịch có nhiều ứng viên, màn hình hỗ trợ cần hiển thị mã tham chiếu nguồn, số tiền và lý do mỗi đơn được đề xuất. partnerRefId chỉ có giá trị mạnh khi đúng phạm vi và mapping đã được xác lập; giao dịch OUT không thể xác nhận một đơn chờ tiền vào. Người xử lý chọn đơn phải để lại lý do và dấu vết phê duyệt. Một khoản chưa đủ bằng chứng nên tiếp tục ở hàng chờ, thay vì chọn đơn đầu tiên để làm báo cáo hết lệch.
Khi kiểm tra một lần gửi lại, đối chiếu accountNumber nguyên bản, khóa chống trùng và trạng thái nghiệp vụ trước đó. Bản giao dịch giữ mốc received_at đầu tiên; từng lần nhận sau có thể được ghi bằng retry_at trong lịch sử riêng. Trên báo cáo sự cố, tách số lần HTTP gửi thành công khỏi số giao dịch được ghi nhận. Cách ghi này giúp thấy receiver đã ACK nhưng worker còn chờ, hoặc nhiều lần gửi chỉ thuộc một giao dịch, mà không cộng sai doanh thu.
Quy trình triển khai API thanh toán ngân hàng Pay2S
- Lấy
user_bank_idtừ danh sách tài khoản Pay2S. - Tạo webhook với URL HTTPS và type phù hợp.
- Lưu webhook token trong secret manager.
- Xác thực token trước khi parse và xử lý payload.
- Lưu raw payload, id hoặc checksum bằng khóa duy nhất.
- Khớp tài khoản, mã tham chiếu, số tiền và trạng thái đơn.
- Trả ACK sau khi dữ liệu đã được lưu bền vững.
- Dùng history và resend cho lỗi, không tạo giao dịch mới.
Câu hỏi thường gặp
Webhook giao dịch nhận những loại nào?
Có thể chọn IN, OUT hoặc ALL. Chọn theo nghiệp vụ để giảm dữ liệu không cần xử lý.
paymentCode có phải mã giao dịch ngân hàng không?
Không. paymentCode là mã thanh toán được trích trong nội dung theo cấu hình; transactionNumber là mã giao dịch ngân hàng.
Có thể coi extraData là bắt buộc không?
Không. extraData là object tùy chọn và dữ liệu phụ thuộc cấu hình cùng ngân hàng.
Gửi lại webhook có tạo giao dịch mới không?
Không. Gửi lại payload lịch sử; receiver phải chống trùng theo ID hoặc checksum.
Có nên phát hành hóa đơn ngay trong webhook không?
Không nên gắn phát hành hóa đơn trực tiếp với việc nhận POST. Sau khi xác minh đơn, số tiền, tài khoản và chống trùng, luồng hóa đơn còn phải kiểm tra dữ liệu cùng thời điểm lập phù hợp nghiệp vụ. Queue giúp theo dõi hai kết quả riêng và xử lý khi chỉ một bước hoàn tất.