Qbit API 通知
本文档列出并描述了Qbit Notifications中使用的事件和数据模型。
WebHook
Qbit 使用 Webhook 按指定格式向客户提供的单一回调 URL 发送通知。Qbit 会根据下文描述的不同场景发送不同的 payload,触发事件可以通过 payload body 中的 eventType 字段来理解。
在收到通知后,需要在 5 秒内返回应答报文。否则,Qbit 会认为通知失败,并重复发送该通知。
同一条通知可能会被发送多次,必须正确处理重复通知。如果该通知已经处理过,则直接向 Qbit 返回成功即可。
你将收到如下所示的 HTTP 请求。
POST /webhook HTTP/1.1
Host: xxx
Content-Type: application/json
Signature-Method: HMAC-SHA256
Signature: D29qUeeyV14HFG6DiuyFRsGLILlxzL8s7okeRMHjzFU=
Timestamp: 1756879969964
{
"eventType": "CARD_TRANSACTION.CREATED",
"apiVersion": "v3",
"code": "000000",
"message": "",
"resource": "{\"id\":\"1234567890\",\"cardId\":\"4111111111111111\",\"createTime\":\"1756879969964\",\"processingCode\":\"00\",\"accountId\":\"ACC987654321\",\"transactionAmount\":\"150.00\",\"transactionCurrency\":\"USD\",\"billingAmount\":\"150.00\",\"billingCurrency\":\"USD\",\"merchantName\":\"Example Store\",\"merchantCity\":\"New York\",\"merchantCountry\":\"USA\",\"transactionType\":\"authorization\",\"mcc\":\"5812\"}",
"createTime": "1756879969964",
"id": "60633733-2b0d-41a2-a6b4-12b3ba085428"
}
客户端需要返回指定的返回码。如果在发送回调请求后未收到对应的返回码,qbit 系统将认为推送失败。返回字段如下:
| Field | Type | Description |
|---|---|---|
| received | boolean | Receiving identifier |
Example:
{
"received": true
}Retry interval
| Retry number | Interval | Retry number | Interval |
|---|---|---|---|
| 1 | 10 seconds | 9 | 7 minutes |
| 2 | 30 seconds | 10 | 8 minutes |
| 3 | 1 minute | 11 | 9 minutes |
| 4 | 2 minutes | 12 | 10 minutes |
| 5 | 3 minutes | 13 | 20 minutes |
| 6 | 4 minutes | 14 | 30 minutes |
| 7 | 5 minutes | 15 | 1 hour |
| 8 | 6 minutes | 16 | 2 hours |
Common Considerations
当前所有通知消息的实现都包含以下属性:
💡 💡 使用 resource 字段进行签名校验
| Name | Type | Description | Sample |
|---|---|---|---|
| id | string | 通知唯一标识 | 32b0216b-66d9-498b-a4bc-17612d9cb6cd |
| eventType | string | 通知的事件类型 | CARD.CREATED |
| createTime | string | 创建时间 | 1757657700094 |
| resource | string | 资源的 JSON 格式 | JSON 格式,使用该字段进行签名校验 |
| apiVersion | string | webhook 版本 | v3 |
| code | string | 状态码信息 | 000000 |
| message | string | 返回信息 | success |
签名
每个由 qbit 发起的请求都包含一个 sign 参数,可用于验证该请求是否来自 qbit。对于每个请求,需要获取 data 参数中的数据,并通过 HMAC-SHA256 哈希函数进行处理。
Example
@Slf4j
public class Example {
private static final String HMAC_SHA256 = "HmacSHA256";
// generate sign
public static String sign(String data, String secret) {
try {
Mac mac = Mac.getInstance(HMAC_SHA256);
SecretKeySpec secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), HMAC_SHA256);
mac.init(secretKey);
byte[] hash = mac.doFinal(data.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
log.error("sign error", e);
return null;
}
}
// verify sign
public static boolean verify(String data, String signature, String secret) {
try {
String computedSignature = sign(data, secret);
return computedSignature != null && computedSignature.equals(signature);
} catch (Exception e) {
return false;
}
}
public static void main(String[] args) {
String data = "{\"a\":\"b\"}";
String secret = "6d8557a0cded4483b8d9c3cea0272cd7";
String signature = sign(data, secret);
System.out.println(signature);
}
//sign = Sj972aD0pmG+zClb7mKoUBZbQd5KlAyxaCKHUSMpBME=
}
const crypto = require('crypto');
/**
* Generates HMAC-SHA256 signature
* @param {string} data - The data to be signed
* @param {string} secret - The secret key for signing
* @returns {string|null} Base64 encoded signature, null if error occurs
*/
function sign(data, secret) {
try {
// Create HMAC-SHA256 hash object
const hmac = crypto.createHmac('sha256', secret);
// Update with data using UTF-8 encoding
hmac.update(data, 'utf8');
// Calculate signature and return as Base64
return hmac.digest('base64');
} catch (error) {
console.error('Signing error:', error);
return null;
}
}
/**
* Verifies HMAC-SHA256 signature
* @param {string} data - The original data
* @param {string} signature - The signature to verify
* @param {string} secret - The secret key for verification
* @returns {boolean} True if verification succeeds, false otherwise
*/
function verify(data, signature, secret) {
try {
const computedSignature = sign(data, secret);
return computedSignature !== null && computedSignature === signature;
} catch (error) {
console.error('Verification error:', error);
return false;
}
}
// Example usage
function main() {
const data = '{"a":"b"}';
const secret = '6d8557a0cded4483b8d9c3cea0272cd7';
const signature = sign(data, secret);
console.log('Generated signature:', signature);
// Verify the generated signature
const isValid = verify(data, signature, secret);
console.log('Signature valid:', isValid);
}
// Run example
main();
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"fmt"
"log"
)
// sign generates an HMAC-SHA256 signature for the given data using the secret key
// Returns the base64-encoded signature or an error if something fails
func sign(data, secret string) (string, error) {
// Create a new HMAC-SHA256 hasher using the secret key
h := hmac.New(sha256.New, []byte(secret))
// Write the data to be signed
_, err := h.Write([]byte(data))
if err != nil {
return "", fmt.Errorf("failed to write data: %w", err)
}
// Calculate the HMAC hash and encode it as base64
signature := base64.StdEncoding.EncodeToString(h.Sum(nil))
return signature, nil
}
// verify checks if the provided signature matches the computed signature for the data
// Returns true if verification succeeds, false otherwise
func verify(data, signature, secret string) bool {
// Compute the expected signature
computedSignature, err := sign(data, secret)
if err != nil {
log.Printf("Verification error: %v", err)
return false
}
// Compare the computed signature with the provided one
return computedSignature == signature
}
func main() {
data := `{"a":"b"}`
secret := "6d8557a0cded4483b8d9c3cea0272cd7"
// Generate signature
signature, err := sign(data, secret)
if err != nil {
log.Fatalf("Failed to generate signature: %v", err)
}
fmt.Println("Generated signature:", signature)
// Verify signature
isValid := verify(data, signature, secret)
fmt.Println("Signature valid:", isValid)
}
📢 账户通知(Account Notification)
事件类型
| Event Type | Description |
|---|---|
| ACCOUNT.REGISTERED | 账户注册,即在系统中创建一个新的账户。 |
资源对象
resource 字段将是 Object。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"apiVersion": "v3",
"code": "000000",
"message": "",
"createTime": "1757659595371",
"eventType": "ACCOUNT.REGISTERED",
"resource": "..."
}📢 CDD 通知(CDD Notification)
事件类型
| Event Type | Description |
|---|---|
| KYB.UPDATED | KYB 状态变更。 |
| KYC.UPDATED | KYC 状态变更。 |
CDD 对象
resource 字段将是 Object。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"apiVersion": "v3",
"code": "000000",
"message": "",
"createTime": "1757659595371",
"eventType": "KYB.UPDATED",
"resource": "..."
}📢 Infinity Card 通知
Card
事件类型
| Event Type | Description |
|---|---|
| CARD.CREATED | 卡已创建。 |
| CARD.UPDATED | 卡信息已更新。 |
| CARD.DELETED | 卡已删除。 |
示例
创建一张 infinity card 后,你应该会在本地服务器终端中看到如下通知消息。
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"eventType": "CARD.CREATED",
"code": "000000",
"message": "",
"resource": "...",
"apiVersion": "v3",
"createTime":"1756879969964"
}resource 字段将是 Card Object 的 JSON 格式。
📢 实体卡通知(Physical Card Notification)
Physical Card
事件类型
| Event Type | Description |
|---|---|
| PHYSICAL_CARD.ACTIVATED | 实体卡已激活。 |
示例
激活一张实体卡后,你应该会在本地服务器终端中看到如下通知消息。
{
"code": "000000",
"message": "success",
"data": {
"id": "ba0a5c6d-ee96-4c44-82f1-dc306d325d8e",
"accountId": "ba0a5c6d-ee96-4c44-82f1-dc306d325d8e",
"cardId": "ba0a5c6d-ee96-4c44-82f1-dc306d325d8e",
"shippingAddress": {
"addressLine1": "123 Main Street",
"addressLine2": "Apt 4B",
"city": "New York",
"state": "NY",
"country": "US",
"postalCode": "10001"
},
"phoneNumber": "13765987596",
"email": "[email protected]",
"phoneCountryCode": "86",
"firstName": "John",
"lastName": "Smith",
"realName": "John Smith",
"realFirstName": "John",
"realLastName": "Smith",
"idNumber": "A123456789",
"idType": "PASSPORT",
"dob": "1985-06-15",
"expirationDate": "2026-12-31",
"displayStatus": "ACTIVE",
"batchNumber": "ba0a5c6d-ee96-4c44-82f1-dc306d325d8e",
"cardStyle": "Plastic"
}
}resource 字段将是 Physical Card Object 的 JSON 格式。
事件类型
| Event Type | Description |
|---|---|
| PHYSICAL_CARD.SHIPPING | 实体卡物流/发货信息。 |
示例
{
"apiVersion": "v3",
"code": "000000",
"createTime": "1767583925529",
"eventType": "PHYSICAL_CARD.SHIPPING",
"id": "57d40040-5218-4504-b991-28193c379d24",
"message": "",
"resource": "{"cardId":"d9485cd6-6753-449f-8cd6-222ba54a84df","expressCompany":"顺丰速运","trackingNumber":"089765463456765"}"
}事件类型
| Event Type | Description |
|---|---|
| PHYSICAL_CARD.VERIFY | 实体卡验证。 |
示例
{
"apiVersion": "v3",
"code": "000000",
"createTime": "1764830001154",
"eventType": "PHYSICAL_CARD.VERIFY",
"id": "92dd0b86-8040-4728-89c6-faefc8293c6b",
"message": "",
"resource": "{"cardId":"ca3a6f77-307b-49aa-ba8c-1d0f9ebebd4c","firstName":"Knope","lastName":"Leslie","status":"Success","reason":"","accountId":"78f4348c-cf66-4578-9ecc-21ace7b96148"}"
}📢 Infinity Account 交易通知(Infinity Account Transaction Notification)
Card
事件类型
| Event Type | Description |
|---|---|
| INFINITY_ACCOUNT_WALLET_TRANSACTION.CREATED | infinity account wallet 交易已创建。 |
示例
创建一条 account wallet transaction 后,你应该会在本地服务器终端中看到如下通知消息。
{
"id": "1f74664d-7680-4038-ac0f-16e62d7d0372",
"accountId": "a1d699e1-d1a7-4125-9953-4f03be19f6b3",
"currency": "USD",
"amount": "100",
"fee": "0",
"type": 1,
"status": "CLOSED",
"createTime": "1755498145103"
}resource 字段将是 InfinityAccountTransaction Resource 的 JSON 格式。
卡交易(Card Transaction)
事件类型
| Event Type | Description |
|---|---|
| CARD_TRANSACTION.CREATED | 卡交易已创建。 |
| CARD_TRANSACTION.UPDATED | 卡交易已更新。 |
示例
当交易发生时,你应该会在本地服务器终端中看到如下通知消息。
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"eventType": "CARD_TRANSACTION.CREATED",
"code": "000000",
"message": "",
"resource": "...",
"apiVersion": "v3",
"createTime":"1756879969964"
}resource 字段将是 Card Transaction Object 的 JSON 格式。
关于卡交易通知中返回的 code 的详细定义,请参考 Card Business Code Reference。
Card Balance Negative
事件类型
| Event Type | Description |
|---|---|
| CARD.BALANCE.NEGATIVE | 卡余额为负。 |
示例
当交易发生时,你应该会在本地服务器终端中看到如下通知消息。
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"eventType": "CARD.BALANCE.NEGATIVE",
"code": "000000",
"message": "",
"resource": "...",
"apiVersion": "v3",
"createTime":"1756879969964"
}resource 字段将是 Card Balance Negative Object 的 JSON 格式。
📢 Budget
事件类型
| Event Type | Description |
|---|---|
| BUDGET.CREATED | budget 已创建。 |
| BUDGET.UPDATED | budget 信息已更新。 |
| BUDGET.DELETED | budget 已删除。 |
示例
创建一个 budget 后,你应该会在本地服务器终端中看到如下通知消息。
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"eventType": "BUDGET.CREATED",
"resource": "...",
"apiVersion": "v3",
"createTime":"1756879969964"
}resource 字段将是 Object 的 JSON 格式。
📢 Budget Transaction
事件类型
| Event Type | Description |
|---|---|
| BUDGET_TRANSACTION.CREATED | budget transaction 已创建。 |
示例
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"eventType": "BUDGET_TRANSACTION.CREATED",
"resource": "...",
"apiVersion": "v3",
"createTime":"1756879969964"
}resource 字段将是 Object 的 JSON 格式。
📢 Cardholder
事件类型
| Event Type | Description |
|---|---|
| CARDHOLDER.CREATED | cardholder 已创建。 |
| CARDHOLDER.UPDATED | cardholder 信息已更新。 |
| CARDHOLDER.DELETED | cardholder 已删除。 |
示例
创建一个 cardholder 后,你应该会在本地服务器终端中看到如下通知消息。
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"eventType": "CARDHOLDER.CREATED",
"resource": "...",
"apiVersion": "v3",
"createTime":"1756879969964"
}resource 字段将是 Object 的 JSON 格式。
📢 支付通知(Payment Notification)
事件类型
| Event Type | Description |
|---|---|
| BUSINESS_TRANSFER.PAYOUT.PAYMENT.CREATED | 支付已创建,但尚未完成。 |
| BUSINESS_TRANSFER.PAYOUT.PAYMENT.UPDATE | 支付已成功完成。与 payout payment 相关的退款也已成功完成。 |
资源对象
resource 字段将是 Object。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"apiVersion": "v3",
"createTime": "1757659595371",
"eventType": "BUSINESS_TRANSFER.PAYOUT.PAYMENT.CREATED",
"resource": "..."
}📢 Business Transfer 通知
事件类型
| Event Type | Description |
|---|---|
| BUSINESS_TRANSFER.CREATED | 订单提交后触发,通知你的系统有一笔新订单。 |
| BUSINESS_TRANSFER.UPDATE | business transfer 失败,未能成功完成。订单完成后也会触发,用于通知你的系统该转账已结束。 |
资源对象
resource 字段将是 Object。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"apiVersion": "v3",
"createTime": "1757649535314",
"eventType": "BUSINESS_TRANSFER.CREATED",
"resource": "..."
}📢 Business Account 通知
事件类型
| Event Type | Description |
|---|---|
| APPLY_LEGAL_ENTITY.CREATED | 当新的 Legal Entity Application(即公司或个人申请开户)成功创建时触发。 |
| APPLY_LEGAL_ENTITY.UPDATE | 当 legal entity 的账户申请状态或详情更新时触发。 |
| APPLY_BANK_ACCOUNT.UPDATE | 当 legal entity 的虚拟账户(VA)申请成功创建时触发。 |
| BUSINESS_ACCOUNT_TRANSACTION.CREATED | 订单提交后触发,通知你的系统有一笔新订单。 |
| BUSINESS_ACCOUNT_TRANSACTION.UPDATED | 需要补充材料或材料被拒绝。 |
Business Account Resource Object
resource 字段将是 Object 。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"createTime": "1757649535314",
"eventType": "CARD.3DS.OTP",
"resource": "..."
}📢 Business Account Transaction Resource Object
事件类型
| Event Type | Description |
|---|---|
| BUSINESS_ACCOUNT_TRANSACTION.CREATED | 订单提交后触发,通知你的系统有一笔新订单。 |
| BUSINESS_ACCOUNT_TRANSACTION.UPDATED | 需要补充材料或材料被拒绝。 |
资源对象
resource 字段将是 Object。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"apiVersion": "v3",
"createTime": "1757649535314",
"eventType": "BUSINESS_ACCOUNT_TRANSACTION.CREATED",
"resource": "..."
}📢 3DS 通知(3DS Notification)
事件类型
| Event Type | Description |
|---|---|
| CARD.3DS.OTP | 该事件会发送卡消费交易的 OTP 详情,包括卡信息、金额以及一次性验证码,以便进行安全验证。 |
资源对象
resource 字段将是 3DS Object。
示例 Payload
{
"id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
"code": "000000",
"message": "",
"createTime": "1757649535314",
"eventType": "CARD.3DS.OTP",
"resource": "..."
}Updated 7 days ago
