Webhook
接收开卡、交易、3DS、共享池等异步事件并验签
Webhook 通知
卡片操作结果、卡交易结果、3DS 验证码、共享池变更等异步事件,由平台以 HTTP POST 推送到你在控制台配置的回调地址。
本平台采用 明文 JSON + RSA 请求头签名,便于服务端验签接入。
1. 基本约定
| 项 | 说明 |
|---|---|
| 请求方式 | POST |
| Content-Type | application/json |
| 回调地址 | 商户自己的公网 URL(控制台 → 开发者 → Webhook,建议 HTTPS)。不是 openapi.cafinx.com / uat-openapi.cafinx.com |
| 成功响应 | 尽快返回 HTTP 2xx 表示已接收 |
| 失败/超时 | 平台延迟多次补发(持久化退避,直至 SUCCESS / DEAD) |
| 幂等 | body 内 eventId 去重(同一事件可能因重试多次到达) |
| 安全 | 请求头 X-Cafinx-Signature:平台对 原始 body 字节 做 RSA-SHA256 签名 |
配置
- 填写公网可达 URL(生产建议 HTTPS)
- 启用开关
- (可选)
eventTypes:订阅列表,逗号分隔;支持card.*、transaction.*、card_group.*;空或*表示全部
平台在保存与投递前做 SSRF 校验(拒绝内网 / 回环 / 云元数据地址)。
推送请求头
| 请求头 | 必填 | 说明 |
|---|---|---|
X-Cafinx-Event | M | 事件类型,同 body.eventType |
X-Cafinx-Timestamp | M | 毫秒时间戳(与 body.timestamp 一致) |
X-Cafinx-Signature | M | Base64( RSA-SHA256( 原始 body, 平台私钥 ) ) |
外层报文(统一信封)
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
eventId | string | M | 平台唯一事件 ID,幂等键 |
eventType | string | M | 事件类型标识(见下表) |
timestamp | long | M | 毫秒时间戳 |
userNo | string | M | 商户号,如 CFX-XXXX |
data | object | M | 业务数据(随事件不同) |
{
"eventId": "evt_8f3a2c1b0d9e",
"eventType": "transaction.authorized",
"timestamp": 1710000000000,
"userNo": "CFX-XXXX",
"data": { }
}开卡/充值等同步接口失败直接返回业务错误码,不会再推送失败类 Webhook。请按 API 响应处理。
2. 事件类型总表
| 分类 | eventType | 说明 |
|---|---|---|
| 交易 | transaction.authorized | 授权/消费成功 |
| 交易 | transaction.declined | 授权拒绝 |
| 交易 | transaction.refunded | 退款 |
| 交易 | transaction.reversed | 冲正 |
| 交易 | transaction.settled | 清算类成功 |
| 卡片操作 | card.issued | 开卡成功 |
| 卡片操作 | card.recharge.succeeded | 卡充值成功 |
| 卡片操作 | card.withdraw.succeeded | 卡转出成功 |
| 卡片操作 | card.frozen / card.unfrozen | 冻结 / 解冻 |
| 卡片操作 | card.closed | 销卡 |
| 3DS | card.3ds.otp | 3DS 验证码(敏感,短时有效) |
| 共享池 | card_group.created | 共享池创建 |
| 共享池 | card_group.recharge.succeeded | 共享池充值 |
| 共享池 | card_group.closed | 共享池注销 |
| 钱包 | recharge.succeeded | 链上钱包充值到账 |
3. 交易结果通知
前置条件
卡发生消费授权、拒绝、退款、冲正或清算等结果并已落库。
接口返回(推送体)
eventType 为 transaction.* 之一;data 字段:
| 字段 | 类型 | 必填 | 字段含义 |
|---|---|---|---|
txId | string | M | 交易记录 ID |
transactionNo | string | M | 交易流水号 |
cardId | string | M | 卡 ID |
cardNoLast4 | string | O | 卡号后四位 |
cardMask | string | O | 掩码卡号 |
type | string | M | 交易类型:AUTH / PURCHASE / REFUND / REVERSAL 等 |
transType | string | O | 细分交易类型码 |
amount | string | M | 金额(十进制字符串) |
currency | string | M | 币种,如 USD |
merchantName | string | O | 商户名称 |
mcc | string | O | 商户 MCC |
status | string | M | APPROVED(通过)/ DECLINED(拒绝)/ SETTLED(已清算)等 |
authCode | string | O | 授权码 |
declineReason | string | O | 拒绝原因(拒绝时) |
fee | string | O | 手续费 |
loadedBalance | string | O | 交易后卡余额 |
示例
{
"eventId": "evt_tx_001",
"eventType": "transaction.authorized",
"timestamp": 1710000000000,
"userNo": "CFX-10086",
"data": {
"txId": "900311",
"transactionNo": "TX20260701123456001",
"cardId": "1001",
"cardNoLast4": "6641",
"type": "AUTH",
"amount": "12.50",
"currency": "USD",
"merchantName": "AMAZON.COM",
"mcc": "5942",
"status": "APPROVED",
"authCode": "A1B2C3",
"fee": "0",
"loadedBalance": "487.50"
}
}4. 卡片操作通知
前置条件
开卡、充值、转出、冻解、销卡或共享池相关操作成功完成。
事件与 data 要点
| eventType | 说明 | data 要点 |
|---|---|---|
card.issued | 开卡成功 | cardId, cardNoLast4, bin, alias, openFee, topup, totalCharge, externalCardId |
card.recharge.succeeded | 卡充值成功 | cardId, amount, fee, feeRate, totalDebit, loadedBalance, walletBalance |
card.withdraw.succeeded | 卡转出成功 | cardId, cardNoLast4, amount, loadedBalance, walletBalance |
card.frozen / card.unfrozen | 冻结 / 解冻 | cardId, cardNoLast4, reason, source, status(FROZEN 已冻结 / ACTIVE 正常) |
card.closed | 销卡 | cardId, cardNoLast4, returnedAmount, status(CLOSED 已销卡) |
card_group.created | 共享池创建 | groupId, externalGroupId, groupName, balance, totalAuthMoney, totalDebit |
card_group.recharge.succeeded | 共享池充值 | groupId, amount, fee, feeRate, totalDebit, balance, walletBalance |
card_group.closed | 共享池注销 | groupId, status(2 已注销), refundedAmount |
示例:开卡
{
"eventId": "evt_card_issued_01",
"eventType": "card.issued",
"timestamp": 1710000000000,
"userNo": "CFX-10086",
"data": {
"cardId": "1001",
"cardNoLast4": "6641",
"bin": "553412",
"alias": "ads-01",
"openFee": "5.00",
"topup": "50.00",
"totalCharge": "55.00",
"externalCardId": "card_ext_xxx"
}
}5. 3DS 通知
前置条件
持卡人发起需 3DS 验证的交易,平台生成 OTP。
事件
| eventType | 说明 |
|---|---|
card.3ds.otp | 3DS 验证码(敏感,约 5 分钟有效) |
data 字段
| 字段 | 类型 | 必填 | 字段含义 |
|---|---|---|---|
otp | string | M | 验证码(或挑战链接类字符串,以平台下发为准) |
cardId | string | M | 卡 ID |
externalCardId | string | O | 外部卡标识 |
cardMask | string | M | 掩码卡号 |
cardNoLast4 | string | O | 后四位 |
merchantName | string | M | 交易商户名 |
transactionAmount | string | O | 交易金额(有则下发) |
transactionCurrency | string | O | 交易币种(有则下发) |
otpId | string | O | OTP 记录 ID |
expireAt | string | O | 过期时间 |
ttlMinutes | number | O | 有效分钟数 |
安全:
otp勿长期明文落库;用完即弃。
资金侧旁路:授权拒绝可能另扣chargeback_fee;部分授权相关费用可能另扣auth_fee(均见GET /wallet/flows,与本 Webhook 独立)。
6. 其它事件
| eventType | 说明 | data 要点 |
|---|---|---|
recharge.succeeded | 链上钱包充值到账 | orderId, asset, amount, txHash? |
7. 验签(务必做)
平台用平台私钥对 body 原文签名。你用平台公钥验签:
- 公钥:控制台,或登录后
GET /v1/developer/app-public-key - 验签对象是收到的原始请求体字节,不要 JSON 反序列化后再 stringify
Node.js
const crypto = require('crypto');
function verifyWebhook(rawBody, signatureBase64, platformPublicKeyPem) {
return crypto.verify(
'RSA-SHA256',
Buffer.from(rawBody, 'utf8'),
platformPublicKeyPem,
Buffer.from(signatureBase64, 'base64'),
);
}Express 请用
express.raw({ type: '*/*' })拿原始 body。
Java
Signature s = Signature.getInstance("SHA256withRSA");
s.initVerify(appPublicKey);
s.update(rawBody.getBytes(StandardCharsets.UTF_8));
boolean ok = s.verify(Base64.getDecoder().decode(signatureB64));8. 响应与重试
| 商户响应 | 平台行为 |
|---|---|
| HTTP 2xx | 标记 SUCCESS,不再重试 |
| 非 2xx / 超时 / 网络失败 | 退避重试:PENDING 返回 FAILED,多次后 DEAD |
未知 eventType | 也请返回 2xx,避免无意义死循环 |
- 控制台可查看投递记录,并对 FAILED/DEAD 手动重发
- 未配置 URL 或未启用时:业务仍成功,Webhook 静默跳过
建议
- 先落库 / 入队再回 2xx,重活异步处理
- 校验时间戳窗口,拒绝过期回放
card.3ds.otp的otp勿长期明文落库- 生产仅使用 HTTPS 回调地址
- 至少订阅:
card.*、transaction.*、card.3ds.otp、card_group.*
Updated about 2 months ago
Did this page help you?
