Skip to content

通知介绍与配置

更新时间: 2026-09-16 21:49

平台会向商户 Portal 中独立配置的 HTTPS Webhook Endpoint 投递已签名的订单最终状态事件。Endpoint 不绑定 API Key, 该地址不需要在每笔代收或代付创建请求中传入。请求 Body 使用 UTF-8 JSON,并固定发送 Content-Type: application/json。 这是平台主动发出的 Webhook 投递,不是商户调用的普通 API 接口。

配置与密钥生命周期

  1. 商户可以创建多个 Endpoint,并为每个 Endpoint 选择事件类型;同一事件类型可以同时投递到多个 Endpoint。新 Endpoint 默认停用。
  2. 创建 Endpoint 时,平台生成 Webhook Secret 和 Webhook Secret ID,当前 Secret 明文显示 60 秒。具备 Webhook Secret 查看权限的商户管理员每次通过新的 Google 验证后可再次查看当前版本;历史、已吊销、已归档或运营暂停版本不能查看。
  3. 商户把 Secret 保存到服务端密钥管理系统,并部署能在处理事件前完成原始 Body 验签的接收端。
  4. 接收端具备可靠接收能力后再独立启用该 Endpoint。接收端示例建议返回 HTTP 200 OK; 平台会将任意 2xx 响应视为接收成功。
  5. 修改 URL 会自动停用 Endpoint,并将待投递记录标记为跳过;新接收端审核完成后再启用。轮换 Secret 前只需停用该 Endpoint,API Key 状态与其无关。
  6. 初次投递和自动重试使用投递记录创建时保存的 URL 与 Secret 版本;商户或运营人工补发使用 Endpoint 当前 URL 与当前 Secret。接收端在最长自动重试周期内应保留旧 Secret ID 映射,并严格按 Webhook-Secret-Id 选择。

商户后台可查看成功、失败和重试中的投递记录,并可在授权后单条补发;系统也会自动重试符合条件的失败投递。

请求 Header

这组 Header 只用于平台调用商户 Webhook 接收地址的通知投递,和商户调用普通 API 时发送的请求 Header 是两套规则。

Header示例描述
Content-Typeapplication/json表示本次 Webhook Body 是 UTF-8 JSON;不参与 Webhook 验签原文。
Request-Idreq_fixture_000000000000000000000001本次投递请求追踪号,用于排障和日志关联。
Webhook-Secret-Idwhsecid_fixture_001本次投递选用的 Webhook Secret 标识;用于在验签前选择本地保存的 Webhook Secret。商户在 Portal 生成或轮换 Webhook Secret 时获取并保存。
Timestamp1780272000本次投递的 Unix 秒级时间戳文本,重试时可能变化。
Noncenonce_fixture_webhook_001本次投递随机串,重试时可能变化。
Signaturev1=<64 lowercase hex>对时间戳、随机串和原始 Body 计算的版本化 HMAC-SHA256 签名。

Header 名称大小写不敏感;文档统一展示无品牌前缀的规范 Header 名,调用时不要自行添加产品、公司或品牌前缀。

HTTP 示例

{merchantWebhookUrl} 是商户在后台配置的完整 HTTPS 通知接收地址,域名和路径均由商户自定义。 代收、代付通知可共用同一个 Endpoint,由 Body eventType 区分,也可以通过不同事件订阅路由到不同 Endpoint;同一事件类型也可由多个 Endpoint 订阅。

示例地址:https://merchant.example.com/

http
POST {merchantWebhookUrl}
Content-Type: application/json
Request-Id: req_fixture_000000000000000000000001
Webhook-Secret-Id: whsecid_fixture_001
Timestamp: 1780272000
Nonce: nonce_fixture_webhook_001
Signature: v1=<64 lowercase hex>

{
  "eventId": "evt_fixture_001",
  "eventType": "PAYMENT_SUCCEEDED",
  "merchantNo": "MCH_FIXTURE_001",
  "order": {
    "platOrderNo": "PHI112606010000000001001",
    "merchantOrderNo": "DEMO_PAY_202606010001",
    "status": "SUCCEEDED",
    "productCode": "PH_PHP_PAYMENT_QRPH_GCASH",
    "country": "PH",
    "amount": {
      "value": "100.50",
      "currency": "PHP"
    },
    "referenceNo": "REF-20260601-0001",
    "customData": {
      "cartId": "CART-10001",
      "customerRef": "CUST-90001"
    },
    "createTime": 1780272000,
    "updateTime": 1780275600
  }
}

签名校验

签名原文固定三行,每一行后都必须追加一个换行字节 \n,包括最后一行:

text
signingPayload = timestamp + "\n" + nonce + "\n" + rawBody + "\n"
signature = "v1=" + lowercase_hex(HMAC-SHA256(webhookSecret, signingPayload))

必须在 JSON 解析、格式化或日志转换之前保留原始 Body。先按接收方配置的时间窗口 检查时间戳,再根据 Webhook-Secret-Id 精确选择本地保存的 Secret。 该 Header 只能作为本地白名单的选择条件,不能直接用于拼接配置项。Secret ID 缺失或未知时 必须直接拒绝处理,禁止降级为逐个尝试全部历史 Secret。

使用常量时间比较期望签名与收到的签名。只有时间戳和签名都校验成功后,才解析并处理 JSON。

WebhookEvent 字段

字段类型必填描述
eventIdstring用于去重的稳定事件 ID。
eventTypestring用于路由业务处理的稳定最终状态事件类型。
merchantNostring本次通知订单所属商户号。
orderobject本次通知携带的订单最终状态数据。
order.platOrderNostring核心交易系统的真实平台订单号,用于技术排障和对账;应作为不透明值使用,不根据格式猜测订单类型。
order.merchantOrderNostring商户侧订单号,用于业务状态更新。
order.statusstring当前订单最终状态。
order.failReasonstring面向商户运营展示或排障的失败/取消原因。order.statusFAILEDCANCELED 时返回;SUCCEEDED 时不返回。
order.productCodestring本笔订单使用的产品编码。
order.countrystringISO 3166-1 alpha-2 两位大写国家码。
order.amountobject订单金额对象。
order.amount.valuestring金额值:交易金额,主币种单位,小数精度以 amount.currency 和所选 productCode 为准。
order.amount.currencystring币种编码:交易币种,使用 ISO 4217 三位大写编码。见支持国家与币种
order.referenceNostring上游支付网络、付款机构或收款机构返回的参考号;用于将付款凭证或收款核实结果关联到订单。
order.customDataobject首次受理创建请求保存的非敏感商户自定义键值;幂等重复请求不会合并或覆盖。
order.recipientSummaryobject脱敏收款账户摘要;代付通知可在有值时返回。
order.recipientSummary.bankCodestring返回 recipientSummary 时必填收款银行编码。
order.recipientSummary.accountNamestring返回 recipientSummary 时必填收款户名。
order.recipientSummary.accountNoLast4string返回 recipientSummary 时必填收款账号后四位。
order.createTimetimestamp订单创建时的 Unix 秒级时间戳,JSON number。
order.updateTimetimestamp当前通知订单状态生效时的 Unix 秒级时间戳,JSON number;同一个事件重试时该值保持不变。

Header 中的 Timestamp 表示本次 HTTP 投递时间,重试时可能变化,并以文本发送。Body 中的 order.updateTime 表示本次通知订单状态的生效时间,以 JSON number 返回,同一事件重试时保持稳定。

Body eventType 使用固定枚举:

eventType含义
PAYMENT_SUCCEEDED代收订单进入成功最终状态。
PAYMENT_CANCELED代收订单在成功前已过期或关闭。
PAYOUT_SUCCEEDED代付订单进入成功最终状态。
PAYOUT_FAILED代付订单进入失败最终状态。

order.referenceNo 不是商户订单号、平台订单号或渠道订单号;它只用于付款凭证核实、收款机构核实和人工排障,不作为普通查单或幂等依据。

投递处理要求

  • 先验签,再按 Body eventId 幂等去重并可靠落库,然后尽快返回任意 HTTP 2xx 响应;无需返回业务数据。
  • 平台会将任意 2xx 响应视为接收成功,并停止该次通知后续重试。即使订单已处理过,商户也应在幂等处理后返回 2xx,避免重复投递。
  • 2xx 响应、接收端超时、连接失败或 TLS 失败会触发自动重试。默认重试窗口为 24 小时,重试节奏约为 1m、2m、4m、8m、16m、32m,之后约每 1h 重试一次。实际投递时间可能有浮动,不承诺秒级准确;超过重试窗口仍失败的事件进入人工补偿。
  • 按 Body eventId 去重;Request-Id 只代表一次投递请求,仅用于排障和日志关联,不能作为业务去重依据。
  • 已接收事件应异步处理,并保证业务消费幂等。Webhook 不保证严格顺序;本地状态推进不明确时,按 merchantOrderNo 查单兜底。
  • 重试可能携带新的 Request-Id、投递时间戳文本、随机串和签名;Body eventIdorder.updateTime 保持原事件值。
  • Webhook-Secret-Id、时间戳或签名校验失败时必须直接拒绝处理;验签成功前不要解析或处理 JSON Payload。

机器可读 Schema 位于 OpenAPI Schemacomponents.schemas.WebhookEvent。可在 代收订单通知代付订单通知 查看渲染后的 Schema 和 Java 验签示例。自行实现 Webhook 验签时,应使用提供的 Webhook 签名测试向量完成校验。