Skip to content

回调通知

平台向创建授权时提供的 callback_url 发送 JSON POST。回调是主链路状态查询只是兜底。

请求头

说明
X-RPA-Event事件类型
X-RPA-Delivery投递记录 ID;重试时保持不变
X-RPA-SignatureCallback Secret 对原始 Body 字节的 HMAC-SHA256

事件

模式事件
授权阶段authorization.completedauthorization.failed
AGGREGATE(默认)order.completedorder.failed
PER_PRODUCTcollection.tax.*collection.invoice.*collection.pdf.*

callback_mode 在创建授权时选择:AGGREGATE 整单一次通知, PER_PRODUCT 按产品分别通知,适合税务和发票分别落库、分别驱动下游流程的系统。

报文

json
{
  "channelOrderNo": "BANK-20260812-0001",
  "orderNo": "1e3f2f55-8da4-4ba9-b941-84860eb1d243",
  "notifyType": 2,
  "event": "order.completed",
  "event_id": "550e8400-e29b-41d4-a716-446655440000:order.completed",
  "attempt_id": "a2ad4ec4-b878-42c5-b623-4d962f350e72",
  "status": 1,
  "data": {}
}
  • 采集或产品结果回调包含顶层 attempt_id;纯授权阶段回调可以没有该字段。
  • 创建授权时传入的 extras_data 会原样回传。
  • 1.5.3 的非阻断部分采集可以发送成功终态,同时在数据中带 qualityStatus=WARNINGcollectionResult=COMPLETED_WITH_WARNINGS 与质量明细;接收端不能只保存事件名。
  • 开启 HTML 报告时,终态回调顶层增量包含 generateHtmlReports=true 与同一份 htmlReports
  • 订单启用 output_encryption=SM4 时,整个 Body 被替换为 SM4-GCM 信封

正确的处理顺序

顺序不能变

  1. 先验签(基于原始字节)
  2. 再解密(如启用 SM4)
  3. 再解析 JSON
  4. event_id 去重
  5. 在同一事务中保存回调记录与业务状态
  6. 事务提交后返回 2xx

平台单次投递超时 10 秒,最多尝试 8 次。 客户端必须容忍重复投递非顺序到达

验签

python
import hashlib
import hmac

def verify(raw_body: bytes, signature_header: str, callback_secret: str) -> bool:
    expected = hmac.new(
        callback_secret.encode("utf-8"), raw_body, hashlib.sha256
    ).hexdigest()
    received = signature_header.removeprefix("sha256=").lower()
    return hmac.compare_digest(expected, received)

必须用原始字节

不得对重新序列化后的 JSON 验签。框架自动解析过的对象再 json.dumps() 得到的字节与原始报文不同,签名必然对不上。

各框架获取原始字节的方式:

框架取法
FastAPI / Starletteawait request.body()
Flaskrequest.get_data()
Spring BootContentCachingRequestWrapperHttpServletRequest.getInputStream()
Expressexpress.raw({ type: 'application/json' })
Ginio.ReadAll(c.Request.Body)

一个完整的接收端

python
import hashlib
import hmac
from fastapi import APIRouter, Header, HTTPException, Request

router = APIRouter()
CALLBACK_SECRET = "..."


@router.post("/rpa/callback")
async def receive(
    request: Request,
    x_rpa_signature: str = Header(default=""),
    x_rpa_event: str = Header(default=""),
):
    raw = await request.body()

    # 1. 验签(原始字节)
    expected = hmac.new(CALLBACK_SECRET.encode(), raw, hashlib.sha256).hexdigest()
    if not hmac.compare_digest(expected, x_rpa_signature.removeprefix("sha256=").lower()):
        raise HTTPException(status_code=401, detail="bad signature")

    # 2. 解密(仅当订单启用 SM4)
    payload = decrypt_if_needed(raw)  # 见「SM4 输出加密」

    # 3. 幂等 + 落库,同一事务
    event_id = payload.get("event_id")
    with begin_transaction() as tx:
        if tx.callback_exists(event_id):
            return {"ok": True}          # 重复投递,直接确认
        tx.save_callback(event_id, x_rpa_event, payload)
        tx.apply_business_state(payload)

    # 4. 提交后返回 2xx
    return {"ok": True}

幂等键怎么选

event_id 的形式是 <authorization_code>:<event>, 在同一授权代次内对同一事件是稳定的。

场景是否会重复幂等策略
平台重试同一次投递event_idX-RPA-Delivery 都不变event_id 去重
平台系统重试或原授权页重新录入凭据event_id 不变、attempt_id 变化event_id 去重,用 attempt_id 更新明细
用户重新授权新的 authorization_code → 新的 event_id视为新代次处理

回调只是信号

回调 data 不保证携带完整业务数据。收到终态回调后,仍应走 产品查询确认 available, 再调用数据接口取数。

联调要点

  • 回调地址必须是公网可达的 HTTPS 地址,且已由平台配置。
  • 接收端应在 10 秒内返回,耗时操作放进异步队列。
  • 主动构造重复投递、错误签名、超时不响应三种场景验证客户端行为。
  • 测试环境与生产环境的回调地址分别配置,不要复用。

适配平台 1.5.3 · 客户交付 R4.4;本文档仅供已签约渠道客户使用。