Webhook sự kiện Hosted Link
✧ Mở bằng AI
Webhook này thông báo tiến trình Hosted Link và trạng thái tài khoản. Webhook giao dịch tiền vào/ra có cấu hình và giao thức riêng.
Cấu hình nơi nhận
Trong Merchant → BankHub → Thiết lập → Webhook event — trạng thái liên kết, lưu URL HTTPS và token webhook. Token gồm 32–128 ký tự chữ, số, _, -; đây là token webhook, không phải API token. Endpoint phải công khai, HTTPS cổng 443; không chuyển hướng sang URL khác.
Một URL có thể nhận cả hai nhóm bên dưới. Phân loại theo event; payload hiện tại không có trường category và không có tùy chọn đăng ký từng nhóm. Return URL chỉ đưa trình duyệt quay lại, không nhận POST webhook.
Hai nhóm sự kiện
Tiến trình phiên
| Event | Ý nghĩa |
|---|---|
LINK_TOKEN_CREATED |
Đã tạo phiên/link |
LINK_SESSION_INITIALIZED |
Khách mở phiên lần đầu |
LINK_SESSION_STARTED |
Khách đồng ý và bắt đầu |
LINK_SESSION_OTP_REQUIRED |
Chờ OTP |
LINK_SESSION_VERIFYING |
Đang xác minh OTP |
LINK_SESSION_COMPLETED |
Hoàn tất phiên; đọc purpose và unlink_mode |
LINK_SESSION_CANCELLED |
Khách hủy phiên |
LINK_SESSION_FAILED |
Phiên thất bại |
LINK_SESSION_EXPIRED |
Phiên hết hạn |
LINK_SESSION_REVOKED |
Phiên bị thu hồi |
LINK_SESSION_UNCERTAIN |
Chưa xác định kết quả, cần đối soát |
LINK_SESSION_STATE_CHANGED |
Thông báo trạng thái tổng quát được giữ để tương thích |
Kết quả và trạng thái tài khoản
| Event | Ý nghĩa |
|---|---|
BANK_ACCOUNT_LINKED |
Liên kết thành công |
BANK_ACCOUNT_UNLINKED |
Đã xác nhận hủy phía ngân hàng; UAT là giả lập |
BANK_ACCOUNT_INACTIVED |
Vô hiệu hóa tại Pay2S, không xác nhận hủy phía ngân hàng |
BANK_ACCOUNT_REACTIVATED |
Bật lại tại Pay2S |
BANK_ACCOUNT_ENABLED |
Bộ đồng bộ quan sát tài khoản hoạt động trở lại |
BANK_ACCOUNT_DISABLED |
Bộ đồng bộ quan sát tài khoản bị vô hiệu hóa |
BANK_ACCOUNT_DISCONNECTED |
Bộ đồng bộ quan sát tài khoản bị xóa/ẩn |
BANK_ACCOUNT_STATE_SYNCED |
Đồng bộ trạng thái ban đầu của tài khoản cũ |
Thông báo quan sát trạng thái có account_status, previous_account_status, bank_unlink_confirmed; không suy ra đã hủy phía ngân hàng khi bank_unlink_confirmed=false. Thay đổi từ màn hình Pay2S bên ngoài Hosted Link được phát hiện qua đối soát định kỳ, có thể trễ hơn event phát trực tiếp từ phiên.
Payload mẫu
{
"event_id": "0123456789abcdef0123456789abcdef",
"version": "1.0",
"timestamp": 1791691200,
"environment": "uat",
"event": "BANK_ACCOUNT_LINKED",
"metadata": {
"session_id": "SESSION_ID",
"application_id": 2,
"customer_reference": "CUSTOMER_DEMO",
"bank_code": "ACB",
"status": "connected",
"purpose": "LINK_BANK_ACCOUNT",
"state": "FINISHED_BANK_ACCOUNT_LINK",
"unlink_mode": "bank",
"account": {
"id": 1,
"bank_code": "ACB",
"account_number": "P2S99999999",
"user_bank_id": 1,
"simulated": 1
},
"bank_profile": "acb_personal",
"account_type": "personal",
"connection_type": "openapi"
},
"sequence": 1
}
event_id xác định một event và giữ nguyên khi retry. sequence là thứ tự phát sinh, có thể có khoảng trống; không đảm bảo thứ tự nhận. timestamp trong body là lúc tạo event, không đổi khi retry. Giữ số tài khoản dưới dạng chuỗi để bảo toàn số 0 đầu.
Các phiên dùng loại ngân hàng mới có thêm metadata.bank_profile, metadata.account_type (personal, business, household) và metadata.connection_type (openapi, rpa). Những trường này bổ sung thông tin, không chứa username, mật khẩu hoặc OTP. Các phiên ACB cá nhân cũ có thể không có các trường bổ sung.
Xác thực webhook
Content-Type: application/json
Authorization: Bearer WEBHOOK_TOKEN
X-Pay2S-Event-Id: EVENT_ID
X-Pay2S-Timestamp: DELIVERY_UNIX_TIMESTAMP
X-Pay2S-Signature: v1=HEX_HMAC_SHA256
Chữ ký là HMAC-SHA256 với khóa bằng token webhook và chuỗi đầu vào:
X-Pay2S-Timestamp + "." + X-Pay2S-Event-Id + "." + raw_request_body
- Kiểm tra Bearer token bằng phép so sánh an toàn.
- Kiểm tra timestamp header; có thể dùng cửa sổ 5 phút nếu đồng hồ server đồng bộ. Mỗi lần gửi có timestamp và chữ ký mới, không kiểm tra độ mới bằng timestamp trong body.
- Tính HMAC trên body nguyên bản, không parse rồi JSON-encode lại; so sánh chữ ký an toàn.
- Parse JSON, đối chiếu event ID header với body,
environment,metadata.application_idvà khách/phiên thuộc hệ thống của bạn. - Lưu event bền vững với khóa duy nhất
(environment, application_id, event_id). Chỉ trả2xxkhi lưu thành công; xử lý nghiệp vụ trong hàng đợi riêng.
Ví dụ công thức PHP (các biến lấy từ header/body nguyên bản):
$expected = 'v1=' . hash_hmac(
'sha256',
$timestampHeader . '.' . $eventIdHeader . '.' . $rawBody,
$webhookToken
);
$validSignature = hash_equals($expected, $signatureHeader);
Đoạn này chỉ minh họa chữ ký, cần thực hiện đầy đủ các bước kiểm tra và lưu bền vững ở trên. Không ghi token, OTP hoặc toàn bộ dữ liệu tài khoản vào log.
Chống trùng và thứ tự
- Cùng
event_id: cùng event được gửi lại; nếu đã lưu thành công, trả2xxvà không chạy nghiệp vụ lần hai. - Khác
event_idnhưng cùng phiên/trạng thái: có thể là các thông báo tương thích cùng mô tả một kết quả. Lọc loại event dùng cho nghiệp vụ như hướng dẫn trên. - Lưu
sequencemới nhất theo phiên hoặc theo tài khoản trong cùng ứng dụng/môi trường. Event cũ vẫn có thể lưu lịch sử nhưng không ghi đè trạng thái mới. - Không so sánh sequence giữa production và UAT. Dùng API tra kết quả phiên để đối soát khi cần.
Khi nào retry và khi nào dừng?
| Kết quả gửi | Xử lý |
|---|---|
HTTP 2xx, không lỗi transport |
delivered, dừng retry |
Timeout/lỗi mạng hoặc HTTP ngoài 2xx |
Tự retry; 3xx không được tự chuyển hướng, 4xx cũng retry hiện tại |
| Hết 8 lần gửi tổng cộng | failed, dừng tự động |
| Thiếu URL/token hoặc ứng dụng không còn đủ quyền hoạt động | skipped, không tự retry |
Khoảng chờ sau mỗi lần thất bại: 1 phút, 2 phút, 5 phút, 10 phút, 30 phút, 60 phút, 120 phút. Mỗi request có timeout tổng 8 giây. HTTP 200 kèm body {"success":false} vẫn được tính thành công: worker không đọc JSON phản hồi.
Nút Gửi lại trong lịch sử sự kiện cho phép gửi lại event failed/skipped sau khi sửa cấu hình; giữ nguyên event ID và đặt lại bộ đếm lần gửi. Cấu hình URL mới không tự phát lại toàn bộ lịch sử skipped.
Hệ thống dùng hàng đợi bền vững và worker chạy liên tục để gửi event mới và retry đến hạn. Không cam kết thời gian giao cố định khi có backlog hoặc endpoint phản hồi chậm. Có thể nhận trùng nếu bên nhận đã lưu nhưng phản hồi bị mất, nên luôn chống trùng.
Kiểm tra với Webhook.site
So sánh event_id trong JSON, không dùng mã request của công cụ. Nhiều request với ID khác nhau có thể là các bước của cùng phiên. Nếu cùng event ID lặp, xem Số lần gửi / HTTP trong lịch sử sự kiện Pay2S. Chỉ gửi thử dữ liệu phù hợp với môi trường thử nghiệm.
Request và response đối chiếu ngày 11/10/2026
Các mẫu dưới đây giữ cấu trúc trường và kiểu dữ liệu. Khóa, chữ ký, URL phiên đã được thay bằng placeholder; danh sách chỉ giữ tối đa hai phần tử và dữ liệu ảnh/PDF/XML dài được rút gọn. Không sao chép placeholder để gọi API thật.
Webhook Hosted Link · Chữ ký & retry
Đối chiếu mã nguồn backend; giá trị minh họa. Payload theo BankHubEvents.php. ACK minh họa từ receiver; webhook có Bearer token và chữ ký HMAC trên raw body.
POST {webhook_url}
Body:
{
"event_id": "0123456789abcdef0123456789abcdef",
"version": "1.0",
"timestamp": 1791691200,
"environment": "uat",
"event": "BANK_ACCOUNT_LINKED",
"metadata": {
"session_id": "SESSION_ID",
"application_id": 2,
"customer_reference": "CUSTOMER_DEMO",
"bank_code": "ACB",
"status": "connected",
"purpose": "LINK_BANK_ACCOUNT",
"state": "FINISHED_BANK_ACCOUNT_LINK",
"unlink_mode": "bank",
"account": {
"id": 1,
"bank_code": "ACB",
"account_number": "P2S99999999",
"user_bank_id": 1,
"simulated": 1
},
"bank_profile": "acb_personal",
"account_type": "personal",
"connection_type": "openapi"
},
"sequence": 1
}
HTTP 200. Response:
{
"success": true
}