HKS Partner VNVN API
    • VNVN Webhook Integration Guide
    • HKS Partner VNVN API
      • Ngân hàng
        • Lấy danh sách ngân hàng liên kết hệ thống
      • Xác minh tài khoản
        • Xác minh tài khoản ngân hàng qua VA
      • Thanh toán
        • Thanh toán / Giải ngân đến tài khoản ngân hàng
      • Giao dịch
        • Danh sách giao dịch của partner
        • Chi tiết giao dịch
      • Tài khoản
        • Thông tin tài khoản VA của partner
      • Schemas
        • TenNganHang
        • NganHangItem
        • DanhSachNganHangResponse
        • XacMinhTaiKhoanRequest
        • ThongTinTaiKhoan
        • KetQuaXacMinh
        • XacMinhTaiKhoanResponse
        • ChuyenKhoanRequest
        • KetQuaChuyenKhoan
        • ChuyenKhoanResponse
        • GiaoDichItem
        • PhanTrang
        • DanhSachGiaoDich
        • DanhSachGiaoDichResponse
        • ChiTietGiaoDichResponse
        • ThongTinVATaiKhoan
        • LỗiValidation
        • ThongTinVATaiKhoanResponse
        • LỗiXácThực
        • LỗiKhôngTìmThấy
        • LỗiQuáNhiềuRequest
        • LỗiMáyChủ
        • LỗiTrùngRequest

    VNVN Webhook Integration Guide

    Hướng dẫn tích hợp webhook cho các sự kiện thanh toán VNVN (nạp tiền / chuyển tiền VND).

    Mục lục#

    Tổng quan
    Đăng ký webhook
    Event Types
    VNVN_PAYMENT_IN
    VNVN_PAYMENT_OUT
    Cấu trúc HTTP Request
    Xử lý webhook
    Retry & Delivery
    Bảo mật
    Test webhook
    FAQ

    Tổng quan#

    Hệ thống webhook VNVN cho phép bạn nhận thông báo HTTP realtime khi có giao dịch VND xảy ra trên tài khoản, bao gồm:
    EventMô tả
    VNVN_PAYMENT_INUser nhận tiền vào tài khoản (nạp tiền từ ngân hàng)
    VNVN_PAYMENT_OUTUser chuyển tiền ra (chuyển khoản tới ngân hàng)
    Webhook được gửi dưới dạng HTTP POST tới URL HTTPS mà bạn đã đăng ký.

    Nguyên tắc:
    Webhook chỉ được gửi sau khi nghiệp vụ hoàn tất
    Với VNVN_PAYMENT_IN: chỉ gửi khi giao dịch không phải idempotent (tránh gửi trùng)
    Với VNVN_PAYMENT_OUT: gửi cả khi SUCCESS lẫn FAILED

    Event Types#

    VNVN_PAYMENT_IN — Nạp tiền#

    Được gửi khi user nhận tiền vào tài khoản thành công (nạp tiền từ ngân hàng qua).
    Khi nào được gửi:
    Balance đã được cộng thành công
    Fee đã được tính và trừ
    Notification đã gửi cho user
    Giao dịch không phải là idempotent (không gửi trùng)
    Payload:
    {
      "transaction_id": "cm3abc123def",
      "amount": 1000000,
      "fee_amount": 10000,
      "final_amount": 990000,
      "balance_after": 5000000,
      "status": "SUCCESS",
      "bank_code": "Vietcombank",
      "bank_name": "NGAN HANG TMCP NGOAI THUONG VIET NAM",
      "bank_account_number": "0123456789",
      "description": "Thanh toan don hang #123",
      "timestamp": "2026-03-09T10:30:00.000Z"
    }
    Chi tiết fields:
    FieldTypeBắt buộcMô tả
    transaction_idstring✅ID giao dịch nội bộ (UserTransaction)
    amountnumber✅Số tiền giao dịch gốc (VND)
    fee_amountnumber✅Phí giao dịch đã tính (VND)
    final_amountnumber✅Số tiền thực nhận = amount - fee_amount
    balance_afternumber✅Số dư tài khoản sau giao dịch (VND)
    statusstring✅Luôn là "SUCCESS"
    bank_codestring❌Mã ngân hàng người chuyển
    bank_namestring❌Tên đầy đủ ngân hàng
    bank_account_numberstring❌Số tài khoản người chuyển
    descriptionstring❌Nội dung chuyển khoản
    timestampstring✅Thời điểm sự kiện (ISO 8601)

    VNVN_PAYMENT_OUT — Chuyển tiền#

    Được gửi khi user thực hiện chuyển khoản tới ngân hàng. Webhook được gửi cả khi thành công và thất bại.

    Trường hợp SUCCESS#

    Khi nào được gửi:
    PayPay API xác nhận chuyển tiền thành công
    UserTransaction status = SUCCESS
    Balance đã trừ + unlock
    Notification đã gửi
    Payload:
    {
      "transaction_id": "cm3def456ghi",
      "amount": 500000,
      "fee_amount": 0,
      "status": "SUCCESS",
      "bank_code": "Vietcombank",
      "bank_name": "NGAN HANG TMCP NGOAI THUONG VIET NAM",
      "bank_account_number": "0987654321",
      "description": "Chuyen tien cho A",
      "timestamp": "2026-03-09T10:35:00.000Z"
    }

    Trường hợp FAILED#

    Khi nào được gửi:
    ITax API trả về lỗi hoặc exception xảy ra
    UserTransaction status = FAILED
    Balance đã unlock (không trừ tiền)
    Payload:
    {
      "transaction_id": "cm3ghi789jkl",
      "amount": 500000,
      "status": "FAILED",
      "error_message": "Insufficient funds in merchant account",
      "bank_code": "Vietcombank",
      "bank_name": "NGAN HANG TMCP NGOAI THUONG VIET NAM",
      "bank_account_number": "0987654321",
      "description": "Chuyen tien cho A",
      "timestamp": "2026-03-09T10:40:00.000Z"
    }
    Chi tiết fields (VNVN_PAYMENT_OUT):
    FieldTypeBắt buộcCó ở SUCCESSCó ở FAILEDMô tả
    transaction_idstring❌✅⚠️ Có thể nullID giao dịch nội bộ
    amountnumber✅✅✅Số tiền chuyển (VND)
    fee_amountnumber❌✅❌Phí giao dịch (VND)
    statusstring✅"SUCCESS""FAILED"Trạng thái giao dịch
    error_messagestring❌❌✅Mô tả lỗi (chỉ khi FAILED)
    bank_codestring❌✅✅Mã ngân hàng người nhận
    bank_namestring❌✅✅Tên ngân hàng người nhận
    bank_account_numberstring❌✅✅Số tài khoản người nhận
    descriptionstring❌✅✅Nội dung chuyển khoản
    timestampstring✅✅✅Thời điểm sự kiện (ISO 8601)

    Cấu trúc HTTP Request#

    Hệ thống gửi HTTP POST tới URL webhook của bạn với body JSON.

    HTTP Headers#

    Content-Type: application/json
    X-Webhook-Topic: vnvn.payment-in.status-updated
    X-HKPay-Event-Signature: base64_encoded_ed25519_signature
    X-Webhook-Id: cm3abc123def456
    X-Webhook-Timestamp: 2026-03-09T10:30:01.000Z
    HeaderMô tả
    Content-TypeLuôn là application/json
    X-Webhook-TopicTopic của event (xem bảng topic bên dưới)
    X-HKPay-Event-SignatureChữ ký Ed25519 (base64) trên toàn bộ request body
    X-Webhook-IdID unique của delivery
    X-Webhook-TimestampThời điểm gửi webhook (ISO 8601)

    Payload#

    Ví dụ VNVN_PAYMENT_IN:
    {
      "id": "cm3abc123def456",
      "topic": "vnvn.payment-in.status-updated",
      "ts": "2026-03-09T10:30:01.000Z",
      "payload": {
        "transaction_id": "cm3abc123def",
        "amount": 1000000,
        "fee_amount": 10000,
        "final_amount": 990000,
        "balance_after": 5000000,
        "status": "SUCCESS",
        "bank_code": "Vietcombank",
        "bank_name": "NGAN HANG TMCP NGOAI THUONG VIET NAM",
        "bank_account_number": "0123456789",
        "description": "Thanh toan don hang #123",
        "timestamp": "2026-03-09T10:30:00.000Z"
      }
    }
    Ví dụ VNVN_PAYMENT_OUT:
    {
      "id": "cm3def456ghi789",
      "topic": "vnvn.payment-out.status-updated",
      "ts": "2026-03-09T10:35:01.000Z",
      "payload": {
        "transaction_id": "cm3def456ghi",
        "amount": 500000,
        "fee_amount": 0,
        "status": "SUCCESS",
        "bank_code": "Vietcombank",
        "bank_name": "NGAN HANG TMCP NGOAI THUONG VIET NAM",
        "bank_account_number": "0987654321",
        "description": "Chuyen tien cho A",
        "timestamp": "2026-03-09T10:35:00.000Z"
      }
    }
    Wrapper fields:
    FieldTypeMô tả
    idstringID unique của delivery (dùng để tra cứu, tránh xử lý trùng)
    topicstringTopic theo event type (xem bảng bên dưới)
    tsstringThời điểm gửi webhook (ISO 8601)
    payloadobjectDữ liệu chi tiết theo event type (xem bảng ở trên)
    Topic theo event type:
    Event TypeTopic
    VNVN_PAYMENT_INvnvn.payment-in.status-updated
    VNVN_PAYMENT_OUTvnvn.payment-out.status-updated
    Phân biệt PAYMENT_IN và PAYMENT_OUT: Dựa vào topic:
    vnvn.payment-in.status-updated → Nạp tiền (PAYMENT_IN)
    vnvn.payment-out.status-updated → Chuyển tiền (PAYMENT_OUT)

    Xử lý webhook#

    Yêu cầu endpoint của bạn#

    1.
    HTTPS bắt buộc — URL phải sử dụng giao thức HTTPS
    2.
    Trả về HTTP 2xx — Hệ thống coi response 200-299 là thành công
    3.
    Xử lý nhanh — Nên trả response trong vòng 10 giây, xử lý nghiệp vụ nặng nên đẩy vào queue riêng
    4.
    Idempotent — Sử dụng id (delivery ID) để tránh xử lý trùng lặp

    Ví dụ xử lý webhook (Node.js / Express)#


    Retry & Delivery#

    Hệ thống tự động retry khi webhook gửi thất bại (HTTP status không phải 2xx hoặc timeout).

    Chiến lược retry#

    Lần thửDelayThời điểm retry
    Lần 1Ngay lập tứcT + 0
    Lần 230 giâyT + 30s
    Lần 32 phútT + 2m30s
    Lần 410 phútT + 12m30s
    Lần 51 giờT + 1h12m30s
    Tối đa 5 lần thử (1 lần gốc + 4 lần retry)
    Nếu sau 5 lần vẫn thất bại → delivery status = FAILED
    Delay tăng theo exponential backoff: 30s → 2m → 10m → 1h → 4h

    Bảo mật#

    Các lưu ý quan trọng#

    1.
    Chỉ HTTPS — Hệ thống không gửi webhook tới URL HTTP không mã hóa
    2.
    Xác minh chữ ký — Luôn verify X-HKPay-Event-Signature để đảm bảo webhook đến từ HKS
    3.
    Idempotency — Luôn kiểm tra id trước khi xử lý để tránh xử lý trùng
    4.
    Timeout — Hệ thống chờ response tối đa 10 giây, sau đó coi như thất bại và retry
    5.
    Không chứa dữ liệu nhạy cảm — Payload webhook không bao gồm mật khẩu, secret key, hay thông tin nhạy cảm

    Xác minh Webhook (Verify Signature)#

    Để đảm bảo tính xác thực của webhook, hãy xác minh chữ ký trong header X-HKPay-Event-Signature bằng Webhook Checksum Key (do HKS cung cấp).
    Chữ ký được tạo bằng thuật toán Ed25519 dựa trên toàn bộ nội dung request body.
    Lưu ý: Webhook Checksum Key là Ed25519 public key (base64 encoded). Bạn nhận key này khi đăng ký webhook endpoint.

    Go Example#

    Java Example#

    Node.js Example#

    Ví dụ sử dụng trong Express#


    FAQ#

    Q: Webhook có gửi khi giao dịch bị trùng (idempotent) không?#

    A: Không. Với VNVN_PAYMENT_IN, nếu giao dịch đã được xử lý trước đó (idempotent response), webhook sẽ không được gửi lại.

    Q: VNVN_PAYMENT_OUT có gửi khi thất bại không?#

    A: Có. Webhook được gửi cả khi SUCCESS và FAILED. Bạn cần kiểm tra field status trong payload để xử lý phù hợp.

    Q: Làm sao phân biệt PAYMENT_IN và PAYMENT_OUT?#

    A: Mỗi event type có topic riêng biệt, không cần phân biệt qua payload:
    vnvn.payment-in.status-updated → Nạp tiền (PAYMENT_IN)
    vnvn.payment-out.status-updated → Chuyển tiền (PAYMENT_OUT)

    Q: Nếu endpoint bị tắt (isActive = false) thì sao?#

    A: Hệ thống sẽ bỏ qua và không tạo delivery record. Khi bật lại, chỉ nhận webhook cho các giao dịch mới, không gửi lại các giao dịch đã bị bỏ qua.

    Q: Webhook có gửi theo thứ tự không?#

    A: Hệ thống cố gắng gửi theo thứ tự, nhưng do retry mechanism, thứ tự có thể không đảm bảo 100%. Sử dụng timestamp trong payload để sắp xếp.
    Modified at 2026-05-12 16:30:10
    Next
    Lấy danh sách ngân hàng liên kết hệ thống
    Built with