Webhook BankHub Pay2S cho trạng thái liên kết là kênh thông báo tiến trình và kết quả khi khách dùng Hosted Link. Nó khác webhook giao dịch tiền vào hoặc tiền ra: một bên nói về session và tài khoản, một bên nói về transaction. Phân biệt hai loại ngay từ thiết kế giúp hệ thống không chạy nhầm nghiệp vụ, không tạo tài khoản trùng và không đánh dấu thanh toán khi mới chỉ có sự kiện giao diện.
Bài viết trên hoadontudong.com.vn tập trung vào cách nhận, xác thực và lưu webhook sự kiện. Nội dung bao gồm hai nhóm event, cấu trúc metadata, chữ ký HMAC trên raw body, event_id, sequence, retry nhiều lần và cách xử lý các thông báo cùng mô tả một kết quả. Đây là một phần quan trọng của API thanh toán ngân hàng Pay2S khi doanh nghiệp muốn liên kết tài khoản mà không phải polling liên tục.
Webhook sự kiện Hosted Link dùng cho việc gì
Hosted Link cho khách nhập thông tin và OTP trên form Pay2S. Sau mỗi bước, Pay2S có thể gửi event về URL HTTPS mà đối tác cấu hình ở Merchant, khu vực BankHub. Một URL có thể nhận cả event tiến trình phiên và event kết quả tài khoản; payload hiện tại không có category để phân loại sẵn, nên receiver phải đọc trường event. Return URL chỉ đưa trình duyệt quay lại và không phải URL nhận POST webhook.
Webhook không thay cho API tra cứu session. Khi nhận event, hệ thống nên lưu payload, xác thực và cập nhật tiến trình, sau đó gọi API để đọc trạng thái chính thức nếu nghiệp vụ cần. Đặc biệt với event uncertain hoặc lỗi, không nên tự tạo phiên liên kết mới. Tra cứu trước giúp biết yêu cầu cũ đang xử lý, đã hoàn tất hay cần nhân viên kiểm tra.
Hai nhóm event phải được xử lý khác nhau
Nhóm tiến trình phiên
LINK_TOKEN_CREATEDLINK_SESSION_INITIALIZEDLINK_SESSION_STARTEDLINK_SESSION_OTP_REQUIREDLINK_SESSION_VERIFYINGLINK_SESSION_COMPLETEDLINK_SESSION_CANCELLEDLINK_SESSION_FAILEDLINK_SESSION_EXPIREDLINK_SESSION_REVOKEDLINK_SESSION_UNCERTAINLINK_SESSION_STATE_CHANGED
Nhóm này phục vụ UI và trạng thái session. Không phải event nào cũng có nghĩa tài khoản ngân hàng đã liên kết thành công.
Nhóm kết quả hoặc trạng thái tài khoản
BANK_ACCOUNT_LINKEDBANK_ACCOUNT_UNLINKEDBANK_ACCOUNT_INACTIVEDBANK_ACCOUNT_REACTIVATEDBANK_ACCOUNT_ENABLEDBANK_ACCOUNT_DISABLEDBANK_ACCOUNT_DISCONNECTEDBANK_ACCOUNT_STATE_SYNCEDBANK_ACCOUNT_LINKED
Khi cần cập nhật quyền tài khoản, ưu tiên event BANK_ACCOUNT_LINKED hoặc event trạng thái phù hợp, đồng thời đọc bank_unlink_confirmed để không nhầm vô hiệu hóa ở Pay2S với hủy thật tại ngân hàng.
Chữ ký HMAC và các bước xác thực receiver

HMAC cho webhookRequest webhook có Content-Type application/json, Authorization: Bearer WEBHOOK_TOKEN, X-Pay2S-Event-Id, X-Pay2S-Timestamp và X-Pay2S-Signature dạng v1=HEX_HMAC_SHA256. Chữ ký dùng token webhook làm khóa và chuỗi timestamp header, dấu chấm, event ID header, dấu chấm, raw request body. Vì vậy receiver phải giữ nguyên bytes body trước khi parse JSON.
Quy trình xác thực nên gồm so sánh token an toàn, kiểm tra timestamp trong cửa sổ thời gian hợp lý, tính lại HMAC trên raw body, so sánh chữ ký an toàn, đối chiếu event ID header với body, kiểm tra environment, application_id và khách hoặc phiên thuộc hệ thống. Không ghi token, OTP hoặc toàn bộ account vào log. Chi tiết xác thực API chiều gọi được tách ở bài xác thực API thanh toán ngân hàng Pay2S.
Chống trùng theo event_id và xử lý sequence
event_id xác định một event và giữ nguyên khi Pay2S retry. Khóa lưu bền vững nên gồm environment, application_id và event_id. Nếu event đã lưu thành công, trả 2xx và không chạy nghiệp vụ lần hai. Nếu receiver đã lưu nhưng phản hồi bị mất, request lặp vẫn phải được coi là thành công. Đây là nguyên tắc quan trọng khi event có thể tạo tài khoản, bật lại dịch vụ hoặc cập nhật quyền.
sequence là thứ tự phát sinh nhưng có thể có khoảng trống và không đảm bảo thứ tự nhận. Lưu sequence mới nhất theo session hoặc account trong cùng môi trường; event cũ có thể giữ trong lịch sử nhưng không ghi đè trạng thái mới. Không so sánh sequence giữa UAT và Production. Khi thứ tự khó xác định, gọi API tra kết quả session để đối soát thay vì đoán từ thời gian nhận.
Retry, ACK và hàng đợi bền vững

Pay2S coi HTTP 2xx không có lỗi transport là đã giao thành công. Timeout, lỗi mạng và response ngoài 2xx có thể được retry; tài liệu nêu khoảng chờ 1 phút, 2 phút, 5 phút, 10 phút, 30 phút, 60 phút và 120 phút, tối đa 8 lần gửi tổng cộng. HTTP 200 kèm body success false vẫn được worker tính là thành công, vì vậy receiver phải trả 2xx sau khi lưu được event và không dùng body ACK như cơ chế duy nhất.
Endpoint nên nhận request nhanh, lưu event vào queue hoặc database bền vững rồi mới xử lý nghiệp vụ nặng. Nếu database tạm thời không ghi được, trả lỗi để Pay2S gửi lại. Nếu event bị failed hoặc skipped sau khi sửa cấu hình, nút gửi lại giữ nguyên event ID và đặt lại bộ đếm. Cấu hình URL mới không tự phát lại toàn bộ lịch sử skipped; cần thao tác rõ ràng.
Các event liên quan nhưng không được chạy lặp nghiệp vụ
Khi một tài khoản liên kết thành công, hệ thống hiện có thể gửi LINK_SESSION_STATE_CHANGED, BANK_ACCOUNT_LINKED và LINK_SESSION_COMPLETED với event_id khác nhau. Đây không nhất thiết là ba lần liên kết. Hãy dùng nhóm session để cập nhật tiến trình và dùng BANK_ACCOUNT_LINKED cho nghiệp vụ cập nhật tài khoản. Nếu xử lý cả ba như lệnh mới, hệ thống có thể tạo quyền trùng, gửi thông báo lặp hoặc gọi API ngân hàng nhiều lần.
Các event BANK_ACCOUNT_INACTIVED, BANK_ACCOUNT_UNLINKED và BANK_ACCOUNT_REACTIVATED cũng cần được phân biệt. Vô hiệu hóa tại Pay2S không khẳng định đã hủy tại ngân hàng; chỉ khi field phù hợp xác nhận mới kết luận. Khi khách đóng form, onExit ở SDK cũng không phải bằng chứng hủy phía server. Bài Hosted Link và SDK Pay2S trình bày quan hệ giữa callback trình duyệt, API session và webhook.
Thiết kế màn hình vận hành và kiểm thử
Màn hình vận hành nên hiển thị session_id, customer_reference, event gần nhất, event_id, environment, status và lỗi an toàn. Nhân viên cần biết event đang chờ OTP, đang xác minh, thành công, thất bại, hết hạn hay uncertain. Không hiển thị Secret Key, webhook token hoặc mật khẩu ngân hàng. Khi một event không thể xác thực chữ ký, lưu mã lỗi nội bộ và cho phép kỹ thuật kiểm tra raw request trong vùng được bảo vệ.
Bộ test nên tạo phiên trên UAT, mở form, đổi ngân hàng, yêu cầu OTP, nhập OTP, hoàn tất, hủy, để hết hạn và mô phỏng lỗi. Kiểm tra event gửi trùng, sequence bị lệch, receiver timeout và gửi lại thủ công. Sau mỗi test, gọi API link session và customer accounts để so sánh. Đừng dùng Webhook.site như nguồn kết luận cuối cùng; công cụ chỉ hỗ trợ nhìn payload thử nghiệm.
Góc nhìn triển khai thực tế
Receiver webhook nên xử lý theo mô hình nhận nhanh, lưu bền vững, xử lý bất đồng bộ. Tầng HTTP kiểm tra header cần thiết, giới hạn kích thước body và xác thực chữ ký trên raw body; nếu hợp lệ thì ghi event cùng environment, application_id và event_id vào kho durable rồi trả 2xx. Worker phía sau mới cập nhật session, mapping tài khoản hoặc thông báo cho người dùng. Nếu chữ ký sai, không ghi sự kiện như đã hợp lệ; nếu database tạm thời không ghi được, trả lỗi để Pay2S retry theo lịch thay vì trả thành công giả.
Sequence giúp theo dõi tiến trình nhưng không nên được hiểu là mọi sự kiện luôn đến đúng thứ tự. Event có thể đến trễ, lặp hoặc có khoảng trống do lỗi mạng. Consumer nên dùng event_id để chống trùng và dùng sequence để cảnh báo, sau đó lấy trạng thái hiện tại từ API khi phát hiện bước nhảy bất thường. Một event COMPLETED đến trước STARTED không nhất thiết cần bị loại nếu chữ ký, session và environment hợp lệ; thay vào đó lưu raw event, cập nhật state theo quy tắc ưu tiên và đánh dấu cần đồng bộ khi cần.
Runbook vận hành cần có các câu hỏi cụ thể: event nào bị từ chối, retry còn bao nhiêu lần, bản ghi đã vào hàng đợi chưa, người dùng đang thấy trạng thái gì và có thể gọi API nào để xác nhận. Tách webhook trạng thái liên kết khỏi webhook giao dịch giúp mỗi consumer có schema và quyền khác nhau. Không dùng một token chung cho mọi môi trường. Khi triển khai API thanh toán ngân hàng Pay2S, việc lưu raw body và checksum xử lý quan trọng hơn việc chỉ lưu một thông báo ngắn trên log.
Kiểm soát chất lượng và vận hành
Xác thực chữ ký webhook cần làm trên đúng raw request body trước khi framework parse hoặc chuẩn hóa JSON. Chuỗi HMAC gồm timestamp header, event ID header và raw body theo định dạng được tài liệu quy định; chỉ cần thay đổi khoảng trắng hoặc thứ tự serialize là chữ ký khác. Sau khi kiểm tra timestamp trong cửa sổ cho phép, so sánh chữ ký bằng phép so sánh an toàn về thời gian. Nếu header event ID không khớp body hoặc environment không đúng, sự kiện phải bị từ chối và ghi lý do đã che thông tin nhạy cảm. Không dùng lại chữ ký cho giao dịch khác.
Kho dedup cần có unique constraint chứ không chỉ kiểm tra rồi insert ở hai câu lệnh tách rời. Hai request giống nhau đến đồng thời phải chỉ tạo một bản ghi hợp lệ. Lưu trạng thái received, processing, processed hoặc rejected để worker có thể retry nội bộ. Khi event sequence bị nhảy, tạo cảnh báo và gọi endpoint trạng thái nếu quy trình yêu cầu; không tự chế một event còn thiếu. Retry của Pay2S có thể kéo dài nhiều mốc thời gian, vì vậy dữ liệu event và kết quả xử lý phải tồn tại đủ lâu để tra cứu.
Một endpoint webhook có thể nhận lưu lượng bất thường, do đó cần giới hạn body, timeout ngắn và hàng đợi có back-pressure. Xử lý nghiệp vụ nặng như gửi email, cập nhật nhiều đơn hoặc phát hành hóa đơn không nên chạy trước khi ACK. Monitoring cần phân biệt chữ ký sai, token sai, lỗi 5xx, timeout và duplicate hợp lệ. Khi triển khai API thanh toán ngân hàng Pay2S ở Production, việc giữ endpoint ổn định trong vài giây đầu sau deploy rất quan trọng vì worker có thể nhận các event đang retry từ phiên bản trước.
Quy trình triển khai API thanh toán ngân hàng Pay2S
- Lưu URL HTTPS và webhook token trong Merchant Bankhub.
- Xác thực
Bearertoken, timestamp và chữ kýHMACtrên raw body. - Đối chiếu
event_idheader vớievent_idtrong JSON. - Kiểm tra environment,
application_idvà session thuộc khách. - Lưu event duy nhất theo environment,
application_idvàevent_id. - Trả
2xxsau khi lưu bền vững, xử lý nghiệp vụ trong queue. - Dùng
BANK_ACCOUNT_LINKEDcho nghiệp vụ tài khoản. - Tra API khi event uncertain hoặc thứ tự
sequencekhông rõ.
Câu hỏi thường gặp
Webhook sự kiện có phải webhook giao dịch không?
Không. Webhook sự kiện báo tiến trình Hosted Link và trạng thái tài khoản; webhook giao dịch báo tiền vào, tiền ra và transaction.
Có được parse JSON trước khi kiểm chữ ký không?
Không nên. Chữ ký tính trên raw request body, vì vậy cần giữ nguyên body trước khi parse.
event_id có đổi khi retry không?
Không. Cùng một event được retry giữ event ID; event khác mô tả cùng phiên có thể có ID khác.
LINK_SESSION_COMPLETED có đủ để cấp quyền không?
Không nhất thiết. Đọc purpose, account và trạng thái thực tế qua API hoặc event tài khoản phù hợp.
Event failed có tự gửi lại vô hạn không?
Không. Có giới hạn retry; sau đó có thể dùng chức năng gửi lại khi đã sửa cấu hình.