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 系统将认为推送失败。返回字段如下:

FieldTypeDescription
receivedbooleanReceiving identifier

Example:

{
  "received": true
}

Retry interval

Retry numberIntervalRetry numberInterval
110 seconds97 minutes
230 seconds108 minutes
31 minute119 minutes
42 minutes1210 minutes
53 minutes1320 minutes
64 minutes1430 minutes
75 minutes151 hour
86 minutes162 hours

Common Considerations

当前所有通知消息的实现都包含以下属性:

💡 💡 使用 resource 字段进行签名校验

NameTypeDescriptionSample
idstring通知唯一标识32b0216b-66d9-498b-a4bc-17612d9cb6cd
eventTypestring通知的事件类型CARD.CREATED
createTimestring创建时间1757657700094
resourcestring资源的 JSON 格式JSON 格式,使用该字段进行签名校验
apiVersionstringwebhook 版本v3
codestring状态码信息000000
messagestring返回信息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 TypeDescription
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 TypeDescription
KYB.UPDATEDKYB 状态变更。
KYC.UPDATEDKYC 状态变更。

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 TypeDescription
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 TypeDescription
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 TypeDescription
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 TypeDescription
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 TypeDescription
INFINITY_ACCOUNT_WALLET_TRANSACTION.CREATEDinfinity 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 TypeDescription
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 TypeDescription
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 TypeDescription
BUDGET.CREATEDbudget 已创建。
BUDGET.UPDATEDbudget 信息已更新。
BUDGET.DELETEDbudget 已删除。

示例

创建一个 budget 后,你应该会在本地服务器终端中看到如下通知消息。

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "code": "000000",
    "message": "",
    "eventType": "BUDGET.CREATED",
    "resource": "...",
    "apiVersion": "v3",
    "createTime":"1756879969964"
}

resource 字段将是 Object 的 JSON 格式。


📢 Budget Transaction

事件类型

Event TypeDescription
BUDGET_TRANSACTION.CREATEDbudget transaction 已创建。

示例

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "code": "000000",
    "message": "",
    "eventType": "BUDGET_TRANSACTION.CREATED",
    "resource": "...",
    "apiVersion": "v3",
    "createTime":"1756879969964"
}

resource 字段将是 Object 的 JSON 格式。

📢 Cardholder

事件类型

Event TypeDescription
CARDHOLDER.CREATEDcardholder 已创建。
CARDHOLDER.UPDATEDcardholder 信息已更新。
CARDHOLDER.DELETEDcardholder 已删除。

示例

创建一个 cardholder 后,你应该会在本地服务器终端中看到如下通知消息。

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "code": "000000",
    "message": "",
    "eventType": "CARDHOLDER.CREATED",
    "resource": "...",
    "apiVersion": "v3",
    "createTime":"1756879969964"
}

resource 字段将是 Object 的 JSON 格式。


📢 支付通知(Payment Notification)

事件类型

Event TypeDescription
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 TypeDescription
BUSINESS_TRANSFER.CREATED订单提交后触发,通知你的系统有一笔新订单。
BUSINESS_TRANSFER.UPDATEbusiness 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 TypeDescription
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 TypeDescription
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 TypeDescription
CARD.3DS.OTP该事件会发送卡消费交易的 OTP 详情,包括卡信息、金额以及一次性验证码,以便进行安全验证。

资源对象

resource 字段将是 3DS Object

示例 Payload

{
    "id": "6a94b9c7-40d6-4007-a5d0-a96d714a1108",
    "code": "000000",
    "message": "",
    "createTime": "1757649535314",
    "eventType": "CARD.3DS.OTP",
    "resource": "..."
}