Webhook

接收开卡、交易、3DS、共享池等异步事件并验签

Webhook 通知

卡片操作结果、卡交易结果、3DS 验证码、共享池变更等异步事件,由平台以 HTTP POST 推送到你在控制台配置的回调地址。

本平台采用 明文 JSON + RSA 请求头签名,便于服务端验签接入。

1. 基本约定

项说明
请求方式POST
Content-Typeapplication/json
回调地址商户自己的公网 URL(控制台 → 开发者 → Webhook,建议 HTTPS)。不是 openapi.cafinx.com / uat-openapi.cafinx.com
成功响应尽快返回 HTTP 2xx 表示已接收
失败/超时平台延迟多次补发(持久化退避,直至 SUCCESS / DEAD)
幂等body 内 eventId 去重(同一事件可能因重试多次到达)
安全请求头 X-Cafinx-Signature:平台对 原始 body 字节 做 RSA-SHA256 签名

配置

  1. 填写公网可达 URL(生产建议 HTTPS)
  2. 启用开关
  3. (可选)eventTypes:订阅列表,逗号分隔;支持 card.*、transaction.*、card_group.*;空或 * 表示全部

平台在保存与投递前做 SSRF 校验(拒绝内网 / 回环 / 云元数据地址)。

推送请求头

请求头必填说明
X-Cafinx-EventM事件类型,同 body.eventType
X-Cafinx-TimestampM毫秒时间戳(与 body.timestamp 一致)
X-Cafinx-SignatureMBase64( RSA-SHA256( 原始 body, 平台私钥 ) )

外层报文(统一信封)

字段类型必填说明
eventIdstringM平台唯一事件 ID,幂等键
eventTypestringM事件类型标识(见下表)
timestamplongM毫秒时间戳
userNostringM商户号,如 CFX-XXXX
dataobjectM业务数据(随事件不同)
{
  "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销卡
3DScard.3ds.otp3DS 验证码(敏感,短时有效)
共享池card_group.created共享池创建
共享池card_group.recharge.succeeded共享池充值
共享池card_group.closed共享池注销
钱包recharge.succeeded链上钱包充值到账

3. 交易结果通知

前置条件

卡发生消费授权、拒绝、退款、冲正或清算等结果并已落库。

接口返回(推送体)

eventType 为 transaction.* 之一;data 字段:

字段类型必填字段含义
txIdstringM交易记录 ID
transactionNostringM交易流水号
cardIdstringM卡 ID
cardNoLast4stringO卡号后四位
cardMaskstringO掩码卡号
typestringM交易类型:AUTH / PURCHASE / REFUND / REVERSAL 等
transTypestringO细分交易类型码
amountstringM金额(十进制字符串)
currencystringM币种,如 USD
merchantNamestringO商户名称
mccstringO商户 MCC
statusstringMAPPROVED(通过)/ DECLINED(拒绝)/ SETTLED(已清算)等
authCodestringO授权码
declineReasonstringO拒绝原因(拒绝时)
feestringO手续费
loadedBalancestringO交易后卡余额

示例

{
  "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.otp3DS 验证码(敏感,约 5 分钟有效)

data 字段

字段类型必填字段含义
otpstringM验证码(或挑战链接类字符串,以平台下发为准)
cardIdstringM卡 ID
externalCardIdstringO外部卡标识
cardMaskstringM掩码卡号
cardNoLast4stringO后四位
merchantNamestringM交易商户名
transactionAmountstringO交易金额(有则下发)
transactionCurrencystringO交易币种(有则下发)
otpIdstringOOTP 记录 ID
expireAtstringO过期时间
ttlMinutesnumberO有效分钟数

安全: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 静默跳过

建议

  1. 先落库 / 入队再回 2xx,重活异步处理
  2. 校验时间戳窗口,拒绝过期回放
  3. card.3ds.otp 的 otp 勿长期明文落库
  4. 生产仅使用 HTTPS 回调地址
  5. 至少订阅:card.*、transaction.*、card.3ds.otp、card_group.*

Did this page help you?