Webhook endpoint
Thêm và quản lý endpoint nhận webhook bằng code, vd. khi dựng môi trường mới. Cùng tính năng với trang Webhook trong app. Cách nhận và verify webhook: Nhận và verify webhook.
Đối tượng webhook_endpoint
Các trường
idstringID endpoint, tiền tốwe_.objectstringLuôn là"webhook_endpoint".urlstringURL nhận webhook, bắt đầu bằnghttps://.descriptionstring | nullMô tả tùy ý.eventsstring[]Loại sự kiện đăng ký, hoặc["*"]cho mọi sự kiện. Xem Sự kiện và payload.bankAccountIdsstring[] | nullChỉ nhận sự kiện của các tài khoản ngân hàng này;nulllà mọi tài khoản. Sự kiện không gắn tài khoản (vd.webhook.test) vẫn gửi.headerNamesstring[]Tên các header tùy chỉnh gửi kèm mỗi request. Giá trị được mã hóa, không trả về.statusenumenabledhoặcdisabled.disabledReasonenum | nullLý do tắt khistatuslàdisabled:manual(bạn tắt),gone(endpoint trả HTTP 410),failing(lỗi liên tục 5 ngày, BankOpenAPI tự tắt). Đang bật thìnull.statusChangedAtstring | nullLần bật/tắt gần nhất, ISO 8601 (UTC);nullnếu chưa đổi từ lúc tạo.createdAtstringThời điểm tạo, ISO 8601 (UTC).
{
"id": "we_8hT2kQ9mZr4cXv1nLp6sYw3e",
"object": "webhook_endpoint",
"url": "https://shop.example.com/webhooks/bankopenapi",
"description": "Web bán hàng (production)",
"events": [
"transaction.received",
"payment_request.paid"
],
"bankAccountIds": null,
"headerNames": [
"X-Api-Key"
],
"status": "enabled",
"createdAt": "2026-11-05T03:00:00.000Z"
}Kiểm tra URL
POST/v1/webhook-endpoints/test-url
URL phải thông mới tạo được endpoint. BankOpenAPI gửi thử một request POST tới URL; endpoint trả 2xx (hoặc 401/403 vì chưa có signing secret để verify) là thông, kèm token dùng làm urlTestToken. Body: url, và headers nếu endpoint cần header xác thực.
curl -X POST https://app.bankopenapi.com/api/v1/webhook-endpoints/test-url \
-H "Authorization: Bearer $BANKOPENAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://shop.example.com/webhooks/bankopenapi"
}'const res = await fetch("https://app.bankopenapi.com/api/v1/webhook-endpoints/test-url", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BANKOPENAPI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://shop.example.com/webhooks/bankopenapi",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);<?php
$ch = curl_init('https://app.bankopenapi.com/api/v1/webhook-endpoints/test-url');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BANKOPENAPI_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://shop.example.com/webhooks/bankopenapi',
]),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception($data['error']['code'] . ': ' . $data['error']['message']);
}
print_r($data);import os
import requests
res = requests.post(
"https://app.bankopenapi.com/api/v1/webhook-endpoints/test-url",
headers={"Authorization": f"Bearer {os.environ['BANKOPENAPI_API_KEY']}"},
json={
"url": "https://shop.example.com/webhooks/bankopenapi",
},
timeout=15,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data){
"object": "url_test",
"url": "https://shop.example.com/webhooks/bankopenapi",
"reachable": true,
"responseStatus": 200,
"durationMs": 182,
"message": null,
"token": "1762331400000.9f2c…"
}Tạo endpoint
POST/v1/webhook-endpoints
Body
urlstringbắt buộcURL công khai,https://, không trỏ vào mạng nội bộ.urlTestTokenstringbắt buộcVé từPOST /v1/webhook-endpoints/test-url(hạn 30 phút, gắn với đúng URL).eventsstring[]bắt buộcÍt nhất một loại sự kiện, hoặc["*"].descriptionstringkhông bắt buộcMô tả, tối đa 200 ký tự.bankAccountIdsstring[]không bắt buộcLọc theo tài khoản ngân hàng (IDba_…). Bỏ trống hoặc[]là mọi tài khoản.headersobject[]không bắt buộcTối đa 5 header{ name, value }, vd. header xác thực hệ thống của bạn yêu cầu. Không đặt đượccontent-type,webhook-*,cf-*.
Trả đối tượng webhook_endpoint (201). Lấy signing secret bằng GET /v1/webhook-endpoints/:id/secret rồi lưu vào biến môi trường của hệ thống. Mỗi tài khoản tối đa 10 endpoint. Nhận header Idempotency-Key.
Lỗi
400 URL_NOT_TESTEDThiếu hoặc saiurlTestToken, hoặc vé đã hết hạn.400 LIMIT_REACHEDĐã có 10 endpoint.
curl -X POST https://app.bankopenapi.com/api/v1/webhook-endpoints \
-H "Authorization: Bearer $BANKOPENAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://shop.example.com/webhooks/bankopenapi",
"urlTestToken": "<token từ test-url>",
"events": ["transaction.received","payment_request.paid"],
"description": "Web bán hàng (production)"
}'const res = await fetch("https://app.bankopenapi.com/api/v1/webhook-endpoints", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BANKOPENAPI_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://shop.example.com/webhooks/bankopenapi",
urlTestToken: "<token từ test-url>",
events: ["transaction.received","payment_request.paid"],
description: "Web bán hàng (production)",
}),
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);<?php
$ch = curl_init('https://app.bankopenapi.com/api/v1/webhook-endpoints');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BANKOPENAPI_API_KEY'),
'Content-Type: application/json',
],
CURLOPT_POSTFIELDS => json_encode([
'url' => 'https://shop.example.com/webhooks/bankopenapi',
'urlTestToken' => '<token từ test-url>',
'events' => ['transaction.received', 'payment_request.paid'],
'description' => 'Web bán hàng (production)',
]),
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception($data['error']['code'] . ': ' . $data['error']['message']);
}
print_r($data);import os
import requests
res = requests.post(
"https://app.bankopenapi.com/api/v1/webhook-endpoints",
headers={"Authorization": f"Bearer {os.environ['BANKOPENAPI_API_KEY']}"},
json={
"url": "https://shop.example.com/webhooks/bankopenapi",
"urlTestToken": "<token từ test-url>",
"events": ["transaction.received","payment_request.paid"],
"description": "Web bán hàng (production)",
},
timeout=15,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data){
"id": "we_8hT2kQ9mZr4cXv1nLp6sYw3e",
"object": "webhook_endpoint",
"url": "https://shop.example.com/webhooks/bankopenapi",
"description": "Web bán hàng (production)",
"events": [
"transaction.received",
"payment_request.paid"
],
"bankAccountIds": null,
"headerNames": [
"X-Api-Key"
],
"status": "enabled",
"createdAt": "2026-11-05T03:00:00.000Z"
}{
"error": {
"code": "URL_NOT_TESTED",
"message": "URL chưa được kiểm tra. …"
}
}Xem, sửa, xóa và các lệnh khác
GET /v1/webhook-endpoints: liệt kê endpoint.GET /v1/webhook-endpoints/:id: xem một endpoint.PATCH /v1/webhook-endpoints/:id: sửaurl(kèmurlTestTokenmới),description,events,bankAccountIds,headers(giá trị để trống là giữ giá trị cũ),status(enabled/disabled).DELETE /v1/webhook-endpoints/:id: xóa endpoint và lịch sử gửi.GET /v1/webhook-endpoints/:id/secret: xem signing secret.POST /v1/webhook-endpoints/:id/rotate-secretvới{ "expiresInHours": 24 }: đổi signing secret. Secret cũ còn được ký kèm thêmexpiresInHoursgiờ (0, 1, 24 hoặc 168; mặc định 24). Trả{ secret, previousSecretExpiresAt }.POST /v1/webhook-endpoints/:id/test: gửi sự kiệnwebhook.testmột lần, không retry.GET /v1/webhook-endpoints/:id/deliveries,…/deliveries/:deliveryId: lịch sử gửi (lưu 30 ngày);POST …/deliveries/:deliveryId/retry: gửi lại.POST /v1/webhook-endpoints/:id/deliveries/retry-failedvới{ "since": "<ISO 8601>" }: gửi lại mỗi lần gửi thất bại từsincevà gửi bù sự kiện phát sinh lúc endpoint tắt (đúng loại và bộ lọc tài khoản của endpoint), mỗi lần gửi đúng một lần, tối đa 500 mỗi lệnh, cũ trước. Trả{ queued, failed, missed, hasMore };hasMorelàtruethì gọi lại sau khi xong.
curl https://app.bankopenapi.com/api/v1/webhook-endpoints \
-H "Authorization: Bearer $BANKOPENAPI_API_KEY"const res = await fetch("https://app.bankopenapi.com/api/v1/webhook-endpoints", {
headers: {
Authorization: `Bearer ${process.env.BANKOPENAPI_API_KEY}`,
},
});
const data = await res.json();
if (!res.ok) throw new Error(`${data.error.code}: ${data.error.message}`);
console.log(data);<?php
$ch = curl_init('https://app.bankopenapi.com/api/v1/webhook-endpoints');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('BANKOPENAPI_API_KEY'),
],
]);
$data = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
curl_close($ch);
if ($status >= 400) {
throw new Exception($data['error']['code'] . ': ' . $data['error']['message']);
}
print_r($data);import os
import requests
res = requests.get(
"https://app.bankopenapi.com/api/v1/webhook-endpoints",
headers={"Authorization": f"Bearer {os.environ['BANKOPENAPI_API_KEY']}"},
timeout=15,
)
data = res.json()
if not res.ok:
raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
print(data){
"object": "list",
"data": [
{
"id": "we_8hT2kQ9mZr4cXv1nLp6sYw3e",
"object": "webhook_endpoint",
"url": "https://shop.example.com/webhooks/bankopenapi",
"description": "Web bán hàng (production)",
"events": [
"transaction.received",
"payment_request.paid"
],
"bankAccountIds": null,
"headerNames": [
"X-Api-Key"
],
"status": "enabled",
"createdAt": "2026-11-05T03:00:00.000Z"
}
]
}