跳到主要内容

概述

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: 交易成功
  • 165802: 续额
  • 165803: 交易失败
  • 165804: 授权成功
  • 165805: 授权失败
165801
event_type
string-事件类型
  • card.transaction.success: 交易成功
  • card.load: 续额
  • card.transaction.failure: 交易失败
  • card.authorization.success: 授权成功
  • card.authorization.failure: 授权失败
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,不要先解析再验签

推送示例

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"
}
}

常见问题(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不是真实卡号,是卡内部编号或卡标识。