Sự kiện và payload

Mỗi webhook có dạng { "id", "type", "livemode", "timestamp", "data" }; livemode là false với sự kiện của sandbox. type cho biết chuyện gì xảy ra, data là đối tượng liên quan ở thời điểm đó, cùng định dạng với API.

Danh sách sự kiện

typeKhi nàodata
transaction.receivedCó tiền vào một tài khoản ngân hàng đã liên kết (báo có).transaction
payment_request.paidYêu cầu thanh toán nhận đủ tiền (status paid, hoặc overpaid nếu chuyển thừa).payment_request
payment_request.underpaidYêu cầu thanh toán nhận được tiền nhưng chưa đủ.payment_request
bank_account.linkedLiên kết xong một tài khoản ngân hàng.bank_account
bank_account.removedMột tài khoản ngân hàng bị hủy liên kết.bank_account
webhook.testSự kiện thử khi bấm "Gửi thử" trên trang Webhook. Chỉ gửi tới endpoint được thử.object

Một khoản chuyển khớp yêu cầu thanh toán sinh ra hai sự kiện: transaction.received và payment_request.paid (hoặc underpaid). Đăng ký loại nào thì nhận loại đó. Hai sự kiện có thể tới theo thứ tự bất kỳ.

transaction.received

Có tiền vào một tài khoản ngân hàng đã liên kết, kể cả khoản không ghi mã TT… nào. Hợp với hệ thống tự khớp tiền theo cách riêng, hoặc để báo có cho team. Chỉ có giao dịch tiền vào; tiền ra không sinh sự kiện.

JSON Schema: transaction.received.json

data: đối tượng transaction

  • idstring
    ID giao dịch trong BankOpenAPI, tiền tố txn_.
  • objectstring
    Luôn là "transaction".
  • livemodeboolean
    false với giao dịch giả lập của sandbox.
  • typestring
    "credit" (tiền vào) hoặc "debit" (tiền ra). Sự kiện transaction.received luôn là "credit".
  • amountinteger
    Số tiền, đơn vị VND.
  • currencystring
    Luôn là "VND".
  • contentstring | null
    Nội dung chuyển khoản ngân hàng gửi về. Ngân hàng thường thêm tên người chuyển, đổi chữ hoa, bỏ dấu. Có ngân hàng không gửi nội dung.
  • referencestring
    Mã tham chiếu giao dịch phía ngân hàng.
  • receivingNumberstring | null
    Tài khoản nhận báo có: virtual account hoặc số tài khoản.
  • paymentRequestIdstring | null
    ID yêu cầu thanh toán đã khớp với giao dịch, null nếu không khớp yêu cầu nào.
  • notifiedAtstring | null
    Thời điểm BankOpenAPI đã báo có giao dịch (webhook, kênh báo có). null nếu chưa báo, ví dụ tiền về lúc báo có tạm dừng.
  • bankAccountobject
    Tài khoản nhận tiền: id, bank.bin, bank.name, accountNumber, label.
  • transactedAtstring
    Thời điểm giao dịch theo ngân hàng.
  • createdAtstring
    Thời điểm BankOpenAPI ghi nhận giao dịch.
transaction.received
{
  "id": "evt_2Gc7Vn4Kp9Wq3Xr8Ty5Bm1Hd",
  "type": "transaction.received",
  "livemode": true,
  "timestamp": "2026-11-05T03:24:51.000Z",
  "data": {
    "id": "txn_5Rk8Wm2Qp7Xn4Bv9Ls3Ht6Jc",
    "object": "transaction",
    "livemode": true,
    "type": "credit",
    "amount": 150000,
    "currency": "VND",
    "content": "NGUYEN VAN BINH chuyen tien TT7K3M9Q",
    "reference": "FT25309871234567",
    "receivingNumber": "0123456789",
    "paymentRequestId": "pr_3fK9mQ2xLwA7nB5vR8tY1cZd",
    "notifiedAt": null,
    "bankAccount": {
      "id": "ba_8Hn2PqR5sT7vW9xY3zA4bC6d",
      "bank": {
        "bin": "970422",
        "name": "MB Bank"
      },
      "accountNumber": "0123456789",
      "label": "MB Bank •••• 6789"
    },
    "transactedAt": "2026-11-05T03:24:50.000Z",
    "createdAt": "2026-11-05T03:24:51.000Z"
  }
}

payment_request.paid

Yêu cầu thanh toán đã nhận đủ tiền. data.status là paid, hoặc overpaid khi người trả chuyển thừa (amountReceived lớn hơn amount). Đây là sự kiện để chuyển đơn sang "đã thanh toán".

JSON Schema: payment_request.paid.json

data là đối tượng payment_request, không kèm mảng transactions. Tìm đơn của bạn theo data.id (đã lưu lúc tạo) hoặc data.orderId.

payment_request.paid
{
  "id": "evt_2Gc7Vn4Kp9Wq3Xr8Ty5Bm1Hd",
  "type": "payment_request.paid",
  "livemode": true,
  "timestamp": "2026-11-05T03:24:52.000Z",
  "data": {
    "id": "pr_3fK9mQ2xLwA7nB5vR8tY1cZd",
    "object": "payment_request",
    "livemode": true,
    "amount": 150000,
    "amountReceived": 150000,
    "currency": "VND",
    "status": "paid",
    "code": "TT7K3M9Q",
    "orderId": "DH1028",
    "description": "Đơn hàng DH1028",
    "bankAccount": {
      "id": "ba_8Hn2PqR5sT7vW9xY3zA4bC6d",
      "bank": {
        "bin": "970422",
        "name": "MB Bank"
      },
      "accountNumber": "0123456789",
      "label": "MB Bank •••• 6789"
    },
    "transfer": {
      "bankBin": "970422",
      "accountNumber": "0123456789",
      "accountName": "NGUYEN VAN AN",
      "amount": 0,
      "content": "TT7K3M9Q"
    },
    "qr": null,
    "hostedUrl": "https://app.bankopenapi.com/thanh-toan/pr_3fK9mQ2xLwA7nB5vR8tY1cZd",
    "paymentLinkId": null,
    "expiresAt": "2026-11-06T03:20:11.000Z",
    "paidAt": "2026-11-05T03:24:52.000Z",
    "canceledAt": null,
    "createdAt": "2026-11-05T03:20:11.000Z"
  }
}

payment_request.underpaid

Yêu cầu nhận được tiền nhưng chưa đủ. data.transfer.amount là số còn thiếu: hiện lại mã VietQR với số này để người trả chuyển thêm (cùng mã). Khi đủ, bạn nhận tiếp payment_request.paid.

JSON Schema: payment_request.underpaid.json

payment_request.underpaid
{
  "id": "evt_2Gc7Vn4Kp9Wq3Xr8Ty5Bm1Hd",
  "type": "payment_request.underpaid",
  "livemode": true,
  "timestamp": "2026-11-05T03:24:52.000Z",
  "data": {
    "id": "pr_3fK9mQ2xLwA7nB5vR8tY1cZd",
    "object": "payment_request",
    "livemode": true,
    "amount": 150000,
    "amountReceived": 100000,
    "currency": "VND",
    "status": "underpaid",
    "code": "TT7K3M9Q",
    "orderId": "DH1028",
    "description": "Đơn hàng DH1028",
    "bankAccount": {
      "id": "ba_8Hn2PqR5sT7vW9xY3zA4bC6d",
      "bank": {
        "bin": "970422",
        "name": "MB Bank"
      },
      "accountNumber": "0123456789",
      "label": "MB Bank •••• 6789"
    },
    "transfer": {
      "bankBin": "970422",
      "accountNumber": "0123456789",
      "accountName": "NGUYEN VAN AN",
      "amount": 50000,
      "content": "TT7K3M9Q"
    },
    "qr": {
      "type": "static",
      "payload": "00020101021238540010A00000072701240006970422011001234567890208QRIBFTTA53037045405500005802VN5913NGUYEN VAN AN62120808TT7K3M9Q6304C187",
      "expiresAt": null
    },
    "hostedUrl": "https://app.bankopenapi.com/thanh-toan/pr_3fK9mQ2xLwA7nB5vR8tY1cZd",
    "paymentLinkId": null,
    "expiresAt": "2026-11-06T03:20:11.000Z",
    "paidAt": null,
    "canceledAt": null,
    "createdAt": "2026-11-05T03:20:11.000Z"
  }
}

bank_account.linked, bank_account.removed

Liên kết xong hoặc hủy liên kết một tài khoản ngân hàng (trong app). Dùng để cập nhật danh sách tài khoản nhận tiền trong hệ thống của bạn, ví dụ lưu data.id để truyền vào bankAccountId.

JSON Schema: bank_account.linked.json, bank_account.removed.json

data: đối tượng bank_account

  • idstring
    ID tài khoản ngân hàng, tiền tố ba_. Dùng làm bankAccountId khi tạo yêu cầu thanh toán.
  • objectstring
    Luôn là "bank_account".
  • livemodeboolean
    false với tài khoản ngân hàng thử của sandbox.
  • bankobject
    bin (mã BIN NAPAS), code (mã viết tắt, vd. MBB), name.
  • accountTypeenum
    personal-account (cá nhân), business-household-account (hộ kinh doanh) hoặc business-account (doanh nghiệp).
  • accountNumberstring
    Số tài khoản.
  • accountNamestring
    Tên chủ tài khoản, in hoa không dấu.
  • receivingNumberstring | null
    Tài khoản nhận báo có: virtual account (tài khoản định danh) hoặc chính số tài khoản.
  • notifyRealAccountboolean
    true nếu nhận báo có ở số tài khoản gốc thay vì virtual account; khi đó receivingNumber là số tài khoản gốc.
  • statusstring
    active (đang liên kết) hoặc removed (đã hủy liên kết, sự kiện bank_account.removed).
  • pendingStepstring | null
    Bước xác nhận còn dang dở; null khi đã liên kết xong.
  • linkedAtstring | null
    Thời điểm liên kết xong.
  • createdAtstring
    Thời điểm bắt đầu liên kết.
bank_account.linked
{
  "id": "evt_2Gc7Vn4Kp9Wq3Xr8Ty5Bm1Hd",
  "type": "bank_account.linked",
  "livemode": true,
  "timestamp": "2026-11-04T09:02:13.000Z",
  "data": {
    "id": "ba_8Hn2PqR5sT7vW9xY3zA4bC6d",
    "object": "bank_account",
    "livemode": true,
    "bank": {
      "bin": "970422",
      "code": "MBB",
      "name": "MB Bank"
    },
    "accountType": "personal-account",
    "accountNumber": "0123456789",
    "accountName": "NGUYEN VAN AN",
    "receivingNumber": "0123456789",
    "notifyRealAccount": false,
    "status": "active",
    "pendingStep": null,
    "linkedAt": "2026-11-04T09:02:13.000Z",
    "createdAt": "2026-11-04T09:00:41.000Z"
  }
}
bank_account.removed
{
  "id": "evt_2Gc7Vn4Kp9Wq3Xr8Ty5Bm1Hd",
  "type": "bank_account.removed",
  "livemode": true,
  "timestamp": "2026-11-20T02:11:40.000Z",
  "data": {
    "id": "ba_8Hn2PqR5sT7vW9xY3zA4bC6d",
    "object": "bank_account",
    "livemode": true,
    "bank": {
      "bin": "970422",
      "code": "MBB",
      "name": "MB Bank"
    },
    "accountType": "personal-account",
    "accountNumber": "0123456789",
    "accountName": "NGUYEN VAN AN",
    "receivingNumber": "0123456789",
    "notifyRealAccount": false,
    "status": "removed",
    "pendingStep": null,
    "linkedAt": "2026-11-04T09:02:13.000Z",
    "createdAt": "2026-11-04T09:00:41.000Z"
  }
}

webhook.test

Gửi khi bạn bấm "Gửi thử" trên trang Webhook, chỉ tới đúng endpoint đó, ký bằng secret của endpoint như sự kiện thật. Không chọn được loại này khi đăng ký. Endpoint của bạn chỉ cần verify và trả 200.

JSON Schema: webhook.test.json

webhook.test
{
  "id": "evt_2Gc7Vn4Kp9Wq3Xr8Ty5Bm1Hd",
  "type": "webhook.test",
  "livemode": true,
  "timestamp": "2026-11-05T02:58:03.000Z",
  "data": {
    "message": "Sự kiện thử từ BankOpenAPI. Endpoint của bạn đã nhận và verify được webhook.",
    "endpointId": "we_6Tz3Nq8Lk2Vb5Xm9Rc4Wp7Jh"
  }
}

JSON Schema

Mỗi loại sự kiện có một JSON Schema (draft 2020-12) mô tả đúng body BankOpenAPI gửi, dùng để kiểm payload trong test, sinh kiểu dữ liệu (TypeScript, Go, Python…) hoặc đưa cho AI agent viết code xử lý webhook. Danh sách:index.json.

Schema cho phép trường mới (additionalProperties), đúng cam kết ở trên: đừng bật kiểm tra chặt hơn trên production, nếu không payload có thêm trường sẽ bị từ chối.

Node.js
// npm install ajv ajv-formats
import Ajv2020 from "ajv/dist/2020.js";
import addFormats from "ajv-formats";

const ajv = new Ajv2020();
addFormats(ajv);
const schema = await (await fetch("https://bankopenapi.com/tai-lieu/webhook/schemas/transaction.received.json")).json();
const validate = ajv.compile(schema);

// event: body đã verify chữ ký (JSON.parse)
if (!validate(event)) console.error(validate.errors);

Xem lại sự kiện qua API

Mọi sự kiện được lưu 30 ngày, kể cả khi chưa có endpoint nào nhận. Dùng để đối chiếu khi hệ thống của bạn lỡ mất webhook, hoặc gửi lại một sự kiện. Cùng dữ liệu với tab "Sự kiện" trên trang Webhook.

  • GET /v1/events: danh sách, mới nhất trước. Lọc ?type=, phân trang limit và starting_after. Mỗi sự kiện có deliveries: số endpoint đã nhận, đang chờ, thất bại.
  • GET /v1/events/:id: một sự kiện kèm mảng deliveries (từng endpoint, trạng thái, mã HTTP).
  • POST /v1/events/:id/resend với { "endpointId": "we_…" }: gửi sự kiện tới một endpoint đang bật ngay lúc gọi, một lần. Cần API key toàn quyền.
Terminal
curl "https://app.bankopenapi.com/api/v1/events?type=transaction.received&limit=20" \
  -H "Authorization: Bearer $BANKOPENAPI_API_KEY"

Bắt đầu với BankOpenAPI

Đăng ký tài khoản, liên kết tài khoản ngân hàng và nhận webhook báo có đầu tiên. Cần tư vấn thì nhắn chúng tôi.