Skip to content

Webhook sự kiện Hosted Link ​

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_INITIALIZEDKhách mở phiên lần đầu
LINK_SESSION_STARTEDKhách đồng ý và bắt đầu
LINK_SESSION_OTP_REQUIREDChờ OTP
LINK_SESSION_VERIFYINGĐang xác minh OTP
LINK_SESSION_COMPLETEDHoàn tất phiên; đọc purpose và unlink_mode
LINK_SESSION_CANCELLEDKhách hủy phiên
LINK_SESSION_FAILEDPhiên thất bại
LINK_SESSION_EXPIREDPhiên hết hạn
LINK_SESSION_REVOKEDPhiên bị thu hồi
LINK_SESSION_UNCERTAINChưa xác định kết quả, cần đối soát
LINK_SESSION_STATE_CHANGEDThô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_LINKEDLiê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_INACTIVEDVô hiệu hóa tại Pay2S, không xác nhận hủy phía ngân hàng
BANK_ACCOUNT_REACTIVATEDBật lại tại Pay2S
BANK_ACCOUNT_ENABLEDBộ đồng bộ quan sát tài khoản hoạt động trở lại
BANK_ACCOUNT_DISABLEDBộ đồng bộ quan sát tài khoản bị vô hiệu hóa
BANK_ACCOUNT_DISCONNECTEDBộ đồ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.

Hành vi hiện tại: nhiều event cho cùng kết quả

Khi liên kết thành công, hệ thống hiện gửi cả LINK_SESSION_STATE_CHANGED, BANK_ACCOUNT_LINKED và LINK_SESSION_COMPLETED với các event_id khác nhau. Khi chờ OTP/xác minh cũng có event cụ thể và event tổng quát. Đây không phải retry.

Đối tác nên cập nhật kết quả liên kết theo BANK_ACCOUNT_LINKED; dùng nhóm phiên để cập nhật tiến trình, không chạy lại nghiệp vụ liên kết cho cả ba thông báo. Việc rút gọn phía gửi thành một event cho mỗi thay đổi chưa được áp dụng trong phiên bản này.

Payload mẫu ​

json
{
  "event": "BANK_ACCOUNT_LINKED",
  "version": "1.0",
  "event_id": "0123456789abcdef0123456789abcdef",
  "metadata": {
    "state": "FINISHED_BANK_ACCOUNT_LINK",
    "status": "connected",
    "purpose": "LINK_BANK_ACCOUNT",
    "unlink_mode": "bank",
    "bank_code": "ACB",
    "session_id": "abcdef0123456789abcdef0123456789",
    "application_id": 2,
    "customer_reference": "CUSTOMER-123",
    "account": {
      "id": 12,
      "bank_code": "ACB",
      "simulated": 0,
      "user_bank_id": 100,
      "account_number": "0123456789"
    }
  },
  "sequence": 61,
  "timestamp": 1791578733,
  "environment": "production"
}

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 ​

http
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:

text
X-Pay2S-Timestamp + "." + X-Pay2S-Event-Id + "." + raw_request_body
  1. Kiểm tra Bearer token bằng phép so sánh an toàn.
  2. 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.
  3. 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.
  4. Parse JSON, đối chiếu event ID header với body, environment, metadata.application_id và khách/phiên thuộc hệ thống của bạn.
  5. Lưu event bền vững với khóa duy nhất (environment, application_id, event_id). Chỉ trả 2xx khi 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):

php
$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ả 2xx và không chạy nghiệp vụ lần hai.
  • Khác event_id như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 sequence mớ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ửiXử lý
HTTP 2xx, không lỗi transportdelivered, dừng retry
Timeout/lỗi mạng hoặc HTTP ngoài 2xxTự 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ộngfailed, dừng tự động
Thiếu URL/token hoặc ứng dụng không còn đủ quyền hoạt độngskipped, 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.