Webhook

接收开卡、交易、3DS、共享池等异步事件并验签(结构对齐行业卡台 Webhook 文档)

Webhook 通知

对标行业卡台(如 GEO)的 Webhook 通知 能力:卡片操作结果、卡交易结果、3DS 验证码、共享池变更等异步事件,由平台 HTTP POST 推送到你在控制台配置的回调地址。

与上游 GEO 的差异:上游对商户侧多使用 type + 加密 data 包;本平台对下游商户采用 明文 JSON + RSA 请求头签名(更易接入)。事件语义一一对应,见下文「与上游 type 对照」。

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,如 transaction.authorizedcard.issued
X-Cafinx-TimestampM毫秒时间戳(与 body.timestamp 一致)
X-Cafinx-SignatureMBase64( RSA-SHA256( 原始 body, 平台 App 私钥 ) )

外层报文(统一信封)

字段类型必填说明
eventIdstringM平台唯一事件 ID,幂等键
eventTypestringM业务类型(见下表)
timestamplongM毫秒时间戳
userNostringM商户号(如 CFX-XXXX,对标上游 userNo)
dataobjectM业务数据(随事件不同,见各节)
{
  "eventId": "evt_8f3a2c1b0d9e",
  "eventType": "transaction.authorized",
  "timestamp": 1710000000000,
  "userNo": "CFX-XXXX",
  "data": { }
}

2. 与上游 type 对照(GEO → CAFINX)

上游 GEO type本平台 eventType说明
card_transactiontransaction.authorized / declined / refunded / reversed / settled卡消费/授权/退款等交易结果
card_operatecard.issued / card.recharge.succeeded / card.withdraw.succeeded / card.frozen / card.unfrozen / card.closed / card_group.*开卡、充值、转出、冻解、销卡、共享池等订单/操作结果
card_3ds_otpcard.3ds.otp3DS 验证码

开卡/充值等同步接口失败直接返回业务错误码,不会再推送 *.failed Webhook(请按 API 响应处理)。


3. 交易结果通知(对标 type = card_transaction

消费授权、拒绝、退款、冲正、清算等,上游推送给平台后转发为下列事件。

3.1 事件一览

eventType说明
transaction.authorized授权/消费成功
transaction.declined授权拒绝/失败
transaction.refunded退款入账成功
transaction.reversed冲正成功
transaction.settled清算/差额结算类成功

3.2 data 字段

字段类型必填说明对标 GEO
txIdstringM本地交易记录 ID
transactionNostringM交易流水号recordNo
cardIdstringM本地卡 IDcardId
cardNoLast4stringO卡号后四位cardNo 掩码一部分
cardMaskstringO掩码卡号card 展示
typestringM交易类型:AUTH / PURCHASE / REFUND / REVERSAL 等transType 映射
transTypestringO上游原始交易类型码transType
amountstringM交易金额(十进制字符串)localCurrencyAmt / transCurrencyAmt
currencystringM币种,如 USDlocalCurrency / transCurrency
merchantNamestringO商户名称merchantName
mccstringO商户 MCCmerchantCategoryCode
statusstringMAPPROVED / DECLINED / SETTLED 等transStatus
authCodestringO授权码approvalCode
declineReasonstringO拒绝原因(declined 时)respCodeDesc
feestringO手续费fee
loadedBalancestringO交易后卡余额

3.3 示例

{
  "eventId": "evt_tx_001",
  "eventType": "transaction.authorized",
  "timestamp": 1710000000000,
  "userNo": "CFX-10086",
  "data": {
    "txId": "900311",
    "transactionNo": "TX20260701123456001",
    "cardId": "1001",
    "cardNoLast4": "6641",
    "cardMask": "**** **** **** 6641",
    "type": "AUTH",
    "transType": "AUTH",
    "amount": "12.50",
    "currency": "USD",
    "merchantName": "AMAZON.COM",
    "mcc": "5942",
    "status": "APPROVED",
    "authCode": "A1B2C3",
    "declineReason": null,
    "fee": "0",
    "loadedBalance": "487.50"
  }
}

4. 订单 / 卡片操作结果(对标 type = card_operate

开卡、充值、转出、冻解、销卡及共享池操作成功后异步通知(对标上游 transactionType 15/16/19/18/26 等)。

4.1 事件一览

eventType说明对标 GEO transactionType(参考)
card.issued开卡成功15 办卡
card.recharge.succeeded卡充值成功16 充值
card.withdraw.succeeded卡转出成功18 退款/转出
card.closed销卡成功19 销卡
card.frozen冻结卡片状态变更
card.unfrozen解冻卡片状态变更
card_group.created共享池创建26 共享卡组申请
card_group.recharge.succeeded共享池充值池充值
card_group.closed共享池注销池关闭

4.2 开卡成功 card.issued — data

字段类型说明
cardIdstring本地卡 ID
cardNoLast4string后四位
binstring卡头
aliasstring别名
openFeestring开卡费
topupstring首充金额
totalChargestring本次总扣款
externalCardIdstring上游卡 ID
providerCodestring上游通道

4.3 卡充值 card.recharge.succeeded — data

字段类型说明
cardIdstring卡 ID
amountstring入卡金额
feestring手续费
feeRatestring费率
totalDebitstring钱包总扣款 amount+fee
loadedBalancestring充值后卡余额
walletBalancestring扣款后钱包余额

4.4 卡转出 card.withdraw.succeeded — data

字段类型说明
cardIdstring卡 ID
cardNoLast4string后四位
amountstring转出金额
loadedBalancestring转出后卡余额
walletBalancestring入账后钱包余额

4.5 冻结 / 解冻 card.frozen / card.unfrozen — data

字段类型说明
cardIdstring卡 ID
cardNoLast4string后四位
reasonstring原因
sourcestringGATEWAY / PLATFORM
statusstringFROZEN / ACTIVE

4.6 销卡 card.closed — data

字段类型说明
cardIdstring卡 ID
cardNoLast4string后四位
returnedAmountstring退回钱包的卡余额
statusstringCLOSED

4.7 共享池

card_group.created

字段说明
groupId本地池 ID
externalGroupId上游池 ID
groupName名称
balance池余额
totalAuthMoney累计授权额度
totalDebit建池时钱包扣款

card_group.recharge.succeeded

字段说明
groupId / amount / fee / feeRate / totalDebit入池与扣款
balance / totalAuthMoney / walletBalance余额快照

card_group.closed

字段说明
groupId池 ID
status2=已注销
refundedAmount退回钱包金额

4.8 示例:开卡

{
  "eventId": "evt_card_issued_01",
  "eventType": "card.issued",
  "timestamp": 1710000000000,
  "userNo": "CFX-10086",
  "data": {
    "cardId": "1001",
    "cardNoLast4": "6641",
    "bin": " rec553412",
    "alias": "ads-01",
    "openFee": "1.00",
    "topup": "50.00",
    "totalCharge": "51.00",
    "externalCardId": "geo_card_xxx",
    "providerCode": "GEO"
  }
}

5. 3DS 通知(对标 type = card_3ds_otp

eventType说明
card.3ds.otp3DS 验证码(敏感,短时有效;文档旧名 card.3ds.challenge 同义)

5.1 data 字段

字段类型必填说明对标 GEO
otpstringM验证码otp
cardIdstringM本地卡 IDcardId
externalCardIdstringO上游卡 ID
cardMask / cardNo 语义stringM掩码卡号cardNo
cardNoLast4stringO后四位
merchantNamestringM交易商户名merchantName
transactionAmountstringO交易金额transactionAmount
transactionCurrencystringO交易币种transactionCurrency
otpIdstringO本地 OTP 记录 ID
expireAtstringO过期时间
ttlMinutesnumberO有效分钟数(约 5)

安全otp 勿长期明文落库;用完即弃。

5.2 示例

{
  "eventId": "evt_3ds_01",
  "eventType": "card.3ds.otp",
  "timestamp": 1710000000000,
  "userNo": "CFX-10086",
  "data": {
    "otpId": "88",
    "cardId": "1001",
    "externalCardId": "geo_card_xxx",
    "cardMask": "**** **** **** 6641",
    "cardNoLast4": "6641",
    "otp": "123456",
    "merchantName": "NETFLIX.COM",
    "transactionAmount": "15.99",
    "transactionCurrency": "USD",
    "expireAt": "2026-07-28T15:30:00",
    "ttlMinutes": 5
  }
}

6. 其它事件

eventType说明data 要点
recharge.succeeded链上钱包充值到账orderId, asset, amount, txHash?

7. 验签(务必做)

平台用平台 App 私钥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

import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.Signature;
import java.util.Base64;

public static boolean verifyWebhook(String rawBody, String signatureB64, PublicKey appPublicKey)
        throws Exception {
    Signature s = Signature.getInstance("SHA256withRSA");
    s.initVerify(appPublicKey);
    s.update(rawBody.getBytes(StandardCharsets.UTF_8));
    return 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.otpotp 勿长期明文落库
  4. 生产仅使用 HTTPS 回调地址
  5. 至少订阅:card.*transaction.*card.3ds.otpcard_group.*

9. 事件总表(速查)

分类eventType状态
交易transaction.authorized / declined / refunded / reversed / settled已上线
卡片操作card.issued / recharge.succeeded / withdraw.succeeded / frozen / unfrozen / closed已上线
3DScard.3ds.otp已上线
共享池card_group.created / recharge.succeeded / closed已上线
钱包recharge.succeeded已上线
同步失败(无 Webhook)看 API 错误码

参考上游文档结构:GEO Webhook 通知


Did this page help you?