代付 webhook
创建代付订单时传入 webhookUrl。订单进入商户可处理的最终状态后,DEEPayment 会向该地址发送明文 JSON webhook,并使用平台 Ed25519 私钥签名(RFC 9421)。body 不加密。
代付 webhook 只投递最终状态。商户必须先验签再处理出款结果,回调重复、延迟或状态冲突时,用查询代付订单接口确认最终状态。SDK 的 parsePayoutWebhook 会完成下面全部校验。
投递状态
SUCCEEDED终态代付成功,会投递 webhook。
FAILED终态代付失败,会投递 webhook。
REFUNDED:原代付成功后,上游退回资金,平台完成商户退款入账后发送。继续使用原代付通知地址和签名协议,退款事件使用独立于原成功通知的新 eventId。
PENDING、PROCESSING 只在查询接口可见,不触发 webhook。内部异常处理状态不会投递给商户;订单恢复到最终状态后再投递最终结果。
HTTP Header
POST /webhook/payout HTTP/1.1
Content-Type: application/json
Content-Digest: sha-256=:base64-sha256-of-body:
Webhook-Event-Id: evt_0123456789ABCDEFGHJKMNPQRT
Signature-Input: platform=("@method" "@path" "@query" "content-type" "content-digest" "webhook-event-id");created=1787803200;expires=1787803500;nonce="b7754a6c-4a9c-4cf0-b77f-6f2d4b7e5f5a";keyid="pwhk_20260827_01";alg="ed25519"
Signature: platform=:base64-ed25519-signature:
Content-Digestsha-256=:base64(SHA-256(rawBody)):,对实际 body 字节计算(RFC 9530)。
Webhook-Event-Id事件 id,恒等于 body 中的 eventId。
Signature-Inputlabel 固定 platform。keyid 对应商户后台展示的平台 webhook 公钥。created、expires 为 Unix 秒,间隔不超过 300 秒。nonce 是 UUID v4。
Signatureplatform=:base64(Ed25519 签名):。
签名规则
签名基串格式与商户请求签名相同(RFC 9421),覆盖字段固定:
"@method": POST
"@path": /webhook/payout
"@query": ?
"content-type": application/json
"content-digest": sha-256=:base64-sha256-of-body:
"webhook-event-id": evt_0123456789ABCDEFGHJKMNPQRT
"@signature-params": ("@method" "@path" "@query" "content-type" "content-digest" "webhook-event-id");created=1787803200;expires=1787803500;nonce="b7754a6c-4a9c-4cf0-b77f-6f2d4b7e5f5a";keyid="pwhk_20260827_01";alg="ed25519"
@path、@query取实际收到的webhookUrl。@query是?加原始 query;URL 没有 query 时为?。- 各行用
\n拼接,末尾不追加换行。最后一行是去掉platform=label 的Signature-Input值。 - 校验:
Ed25519.Verify(platformWebhookPublicKey[keyid], signatureBase, signature)。
每次投递都重新签名:重试时 created、expires、nonce、Signature 都会变化,Webhook-Event-Id 和 body 保持不变。商户按 eventId 去重。
事件示例
{
"eventId": "evt_0123456789ABCDEFGHJKMNPQRT",
"orderType": "PAYOUT",
"orderNo": "FO202605060001",
"merchantOrderNo": "P202605060001",
"status": "SUCCEEDED",
"currency": "BRL",
"amount": "100.00",
"channelTradeNo": "E1234567891",
"attach": "withdraw_123"
}
字段说明
eventId通知事件 id,同一事件的重试投递复用同一个值;与 Header Webhook-Event-Id 相同。
orderType固定为 PAYOUT。
orderNoDEEPayment 代付订单号。
merchantOrderNo商户代付订单号。
status代付最终状态。
currency订单币种。
amount订单金额。
channelTradeNo通道、清算网络或支付网络返回的交易号;BRL PIX 映射 End-to-end ID。
attach商户创建订单时传入的附加信息。
代付 Webhook 不返回出款方或收款方身份信息;渠道提供的代付账户信息通过代付回单接口获取。
验签示例
代付 webhook 的 Header 和签名基串与代收 webhook 完全相同,验签代码见 代收 webhook 的验签示例,只有 orderType 和 body 字段不同。
商户处理顺序
- 读取原始请求
保留 HTTP raw body 原始字节、请求 path 和原始 query 用于验签。
- 校验签名
重算
Content-Digest,确认Webhook-Event-Id等于 bodyeventId,校验时间窗口和 nonce,按keyid选择平台公钥,对重建的签名基串校验Signature。任一步失败直接拒绝,不解析业务字段。 - 幂等落库
以
eventId做投递去重,按orderNo或merchantOrderNo对订单状态更新做业务幂等,放款结果以终态为准。 - 返回 2xx
商户响应任意 HTTP 2xx 视为成功,body 内容不作要求。非 2xx 会触发重试。
状态冲突处理
如果本地状态与 webhook 状态冲突,或者同一订单收到不同终态事件,先冻结本地自动处理并调用查询代付订单接口确认。放款、对账等动作应以已验签 webhook 和查询接口共同确认后的终态为准。
代付退回
{
"eventId": "evt_00000000000000000000000002",
"orderType": "PAYOUT",
"orderNo": "PO202609240001",
"merchantOrderNo": "M202609240001",
"status": "REFUNDED",
"currency": "MXN",
"amount": "100.00",
"refundNo": "R202609240001",
"refundAmount": "100.00",
"refundTime": 1790208000000
}
refundNo:退款单号。refundAmount:全额退款本金,币种主单位的十进制字符串;不是扣除退款费后的余额净增加额。refundTime:退款实际入账时间,Unix 毫秒;重试时不改变。
原代付查单接口返回相同的 REFUNDED 和退款字段;退款尚未入账时保留原支付结果,不返回退款字段。商户按原订单或退款单防重,同一退款只能入账一次;晚到的原成功通知不能覆盖已退款状态。退款事件重试复用自身 eventId 和正文,不覆盖原成功事件。
自动通知在收到 HTTP 2xx 或退款通知创建满 24 小时后停止重试;期限从退款通知创建时间计算,不从原代付订单创建时间计算。通知失败记录保留,商户仍可通过原代付查单接口确认退款结果。
商户只有在处理完成或任务可靠入队后才能返回 HTTP 2xx;入队失败必须返回实际非 2xx,不能仅在 HTTP 200 的正文中返回错误。商户受理后自行重试临时查单和处理失败。
本能力仅支持普通代付全额退回,不新增申请退款接口,不涉及代收或部分退款。