概述
OPCard 平台在卡交易发生后,会通过Webhook异步推送方式,将交易事件实时通知到商户配置的回调地址。商户服务端需接收并处理推送,实现订单状态同步、账务核对和风控监控。
适用场景:
- 商户需要实时同步卡交易状态到自有系统
- 商户需要监听退款、拒付、续额等关键事件
- 商户需要构建统一的资金变动流水
推送机制
推送流程
┌──────────────┐ 交易完成 ┌──────────────┐ HTTP POST ┌──────────────┐
│ OPCard 系统 │ ──────────→ │ 推送服务 │ ──────────→ │ 商户回调地址 │
└──────────────┘ └──────────────┘ + 签名头 └──────────────┘
│ + 验签
↓
┌─────────────────┐
│ 返回 200 OK │
└─────────────────┘
关键约定
| 项 | 说明 |
|---|---|
| 推送方式 | HTTP POST(application/json) |
| 推送地址 | 设置Webhook URL |
| 重试机制 | 最大重试12次,重试阶梯(单位:分钟、小时):1m、2m、4m、8m、15m、30m、1h、2h、4h、8h、16h、32h |
| 推送时机 | 交易状态变更时实时推送 |
| 成功标识 | 商户返回 HTTP 200,其他视为失败 |
| 幂等保证 | notify_event_id全局唯一,商户需入库去重 |
| 字符集 | UTF-8 |
| 推送时区 | UTC(请按需转换) |
事件流转说明
订阅事件从生成、推送到商户处理的完整流转过程。
推送字段说明
| 参数名 | 类型 | 长度 | 是否必填 | 描述 | 示例 |
|---|---|---|---|---|---|
event_code | string | - | 是 | 事件编码,用于标识具体事件类型
| 165801 |
event_type | string | - | 是 | 事件类型
| card.transaction.success |
notify_event_id | long | - | 是 | 通知事件唯一 ID,可用于幕等处理、日志追踪和问题排查 | 2062493598283964416 |
+event_payload | object | - | 是 | 事件业务数据对象,包含卡交易相关的详细信息 |
签名验证
算法与请求头
| 项 | 值 |
|---|---|
| 算法 | HMAC-SHA256(默认) / SHA256withRSA |
| 签名计算规则 | HMAC(secret_key, timestamp + "\n" + payload) |
| 请求头 | X-ODP-Timestamp, X-ODP-Signature |
验签步骤(商户服务端必须实现)
# Python示例
import hmac
import hashlib
def verify_webhook(headers: dict, body: bytes, secret_key: str) -> bool:
timestamp = headers.get("X-ODP-Timestamp", "")
signature = headers.get("X-ODP-Signature", "")
# 1. 拼接签名原文(中间是 "\n",不是空字符串!)
message = timestamp + "\n" + body.decode("utf-8")
# 2. HMAC-SHA256 签名
expected = hmac.new(
secret_key.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256
).hexdigest()
# 3. 比对签名
return hmac.compare_digest(expected, signature)
验签注意事项
| 风险点 | 应对措施 |
|---|---|
| 签名原文用错分隔符 | 必须用 "\n"(换行符),不能用空字符串或空格 |
| 密钥泄露 | secret_key 严禁硬编码到前端/客户端 |
| 重放攻击 | 校验 X-ODP-Timestamp 与服务器时间差(建议 ≤ 5 分钟) |
| Body被修改 | 验签必须基于原始 body,不要先解析再验签 |
推送示例
Header
X-ODP-Timestamp: 1752019200
X-ODP-Signature: 4f7e3d2c1b0a9f8e7d6c5b4a3f2e1d0c...
Body
- 交易成功
- 交易失败
- 授权成功
- 授权失败
- 续额通知
{
"event_code": "165801",
"event_type": "card.transaction.success",
"notify_event_id": 2076942421039628288,
"event_payload": {
"billing_amount": "HKD 0.09",
"creation_time": "2026-04-03 15:04:25",
"transaction_id": "1112002320282",
"account_id": "1016X10007748930412",
"program_value": 1,
"transaction_amount": "HKD 0.09",
"transaction_source": "Online",
"merchant_name": "401 Congress",
"transaction_type": "Purchase",
"card_number": "545502 **** 1159",
"originalTransactionId": "1112002320265",
"card_id": "1"
}
}
{
"event_code": "165803",
"event_type": "card.transaction.failure",
"notify_event_id": 2077647521291722752,
"event_payload": {
"billing_amount": "HKD 0.09",
"creation_time": "2026-04-03 15:04:25",
"transaction_id": "1112002320292",
"account_id": "1016X10007748930412",
"program_value": 8,
"transaction_amount": "HKD 0.09",
"transaction_source": "In Wallet ATM",
"merchant_name": "401 Congress",
"error_cause": "test",
"transaction_type": "Load",
"card_number": "545502 **** 1159",
"card_id": "1"
}
}
{
"event_code": "165804",
"event_type": "card.authorization.success",
"notify_event_id": 2077672457305612288,
"event_payload": {
"billing_amount": "HKD 0.09",
"creation_time": "2026-04-03 15:04:25",
"account_id": "1016X10007748930412",
"program_value": 8,
"transaction_amount": "HKD 0.09",
"merchant_name": "401 Congress",
"error_cause": "test",
"card_number": "545502 **** 1159",
"card_id": "1"
}
}
{
"event_code": "165805",
"event_type": "card.authorization.failure",
"notify_event_id": 2077672457305612288,
"event_payload": {
"billing_amount": "HKD 0.09",
"creation_time": "2026-04-03 15:04:25",
"account_id": "1016X10007748930412",
"program_value": 8,
"transaction_amount": "HKD 0.09",
"merchant_name": "401 Congress",
"error_cause": "test",
"card_number": "545502 **** 1159",
"card_id": "1"
}
}
{
"event_code": "165802",
"event_type": "card.load",
"notify_event_id": 2076942428406169600,
"event_payload": {
"billing_amount": "HKD 0.09",
"creation_time": "2026-04-03 15:04:25",
"transaction_id": "1112002320297",
"account_id": "1016X10007748930412",
"program_value": 8,
"transaction_type": "Exchange",
"card_id": "1"
}
}
常见问题(FAQ)
Q1:推送地址必须 HTTPS吗? A:是的。生产环境必须 HTTPS,且证书有效。
Q2:商户不返回200会怎样? A:会进入重试队列。建议在4秒内返回,业务处理异步执行。
Q3:推送会乱序到达吗?
A:可能。商户需用 transaction_id关联,不依赖推送顺序。
Q4:同一交易会推送多次吗?
A:会。用 notify_event_id 幂等,同一 ID只处理一次。
Q5:推送频率有限制吗? A:暂无限流,但请商户保证3秒内返回。
Q6:transaction_amount 一定是两位小数吗?
A:是的。请商户用BigDecimal解析,避免浮点误差。
Q7:卡号是完整传递吗?
A:card_no不是真实卡号,是卡内部编号或卡标识。