回调说明
当虚拟信用卡发生交易时,系统会向您配置的回调地址发送 POST 请求,实时通知交易信息。
回调请求
请求方式
请求头
Content-Type: application/json
请求体参数
交易数据对象
交易类型(Consumption, Recharge, CashOut, Credit 等)
交易状态(Pending, Finish, Failed)
请求示例
{
"accountId": "132456789",
"data": {
"id": "a7787ada1123-xxxx-uuuuu-sssss",
"cardNum": "5572710152044****",
"type": "Consumption",
"status": "Pending",
"amount": "-25.50",
"merchantName": "Amazon",
"transactionId": "TXN20231201123456",
"recordTime": "2023-12-01T10:30:00.000+00:00",
"remark": "在线购物"
},
"timestamp": "1701424200000",
"sign": "ABC123DEF456..."
}
签名验证
重要:必须验证回调请求的签名,确保请求来自皮卡宝平台
验证步骤
构建验证字符串
将 accountId、data 对象内的所有字段、timestamp 按照 ASCII 码排序并拼接注意:data 对象内的参数也要参与排序,空值字段也要参与签名
对比验证
将计算的签名与请求中的签名对比,一致则验证通过
签名验证示例
function verifyWebhookSign(requestBody, secretKey) {
const { sign, ...params } = requestBody;
// 展开 data 对象
const { data, accountId, timestamp } = params;
const allParams = { accountId, timestamp, ...data };
// 按 ASCII 排序并构建字符串
const sortedKeys = Object.keys(allParams).sort();
const stringA = sortedKeys
.map(key => `${key}=${encodeURIComponent(allParams[key])}`)
.join('&')
.replace(/\+/g, '%20');
// 拼接密钥并计算 MD5
const stringSignTemp = `${stringA}&key=${secretKey}`;
const calculatedSign = md5(stringSignTemp).toUpperCase();
return calculatedSign === sign;
}
import hashlib
from urllib.parse import quote
def verify_webhook_sign(request_body, secret_key):
sign = request_body.pop('sign')
# 展开 data 对象
data = request_body.pop('data')
all_params = {
'accountId': request_body['accountId'],
'timestamp': request_body['timestamp'],
**data
}
# 按 ASCII 排序
sorted_keys = sorted(all_params.keys())
string_a = '&'.join([
f"{key}={quote(str(all_params[key]))}"
for key in sorted_keys
]).replace('+', '%20')
# 拼接密钥并计算 MD5
string_sign_temp = f"{string_a}&key={secret_key}"
calculated_sign = hashlib.md5(
string_sign_temp.encode()
).hexdigest().upper()
return calculated_sign == sign
响应要求
接收到回调后,您的服务器应返回:
成功响应
{
"code": 0,
"msg": "success"
}
失败响应
{
"code": 1,
"msg": "error message"
}
如果回调失败(网络错误、超时、返回非成功状态),系统会进行重试,最多重试 3 次
重试机制
- 重试间隔:5秒、30秒、300秒
- 重试次数:最多 3 次
- 超时时间:10 秒
最佳实践
回调处理示例
// Express.js 示例
app.post('/webhook/vcc', async (req, res) => {
try {
// 1. 验证签名
if (!verifyWebhookSign(req.body, SECRET_KEY)) {
return res.status(403).json({ code: 1, msg: '签名验证失败' });
}
// 2. 检查是否已处理(幂等性)
const { id } = req.body.data;
if (await isProcessed(id)) {
return res.json({ code: 0, msg: 'success' });
}
// 3. 快速响应
res.json({ code: 0, msg: 'success' });
// 4. 异步处理业务逻辑
processTransactionAsync(req.body.data);
} catch (error) {
console.error('Webhook error:', error);
res.status(500).json({ code: 1, msg: '处理失败' });
}
});
常见问题
在用户信息中的 notifyUrl 字段配置,可通过平台后台或 API 设置
所有类型的交易都会触发回调,包括消费、充值、退款、撤销等
可以在测试环境进行小额消费测试,验证回调是否正常接收和处理
系统会自动重试 3 次,如果都失败,需要通过交易查询接口主动查询