Skip to main content
已发布的页面给出了Webhook 通知的结构和重试计划: Webhook 通知概览、 Webhook 通知重试、 Webhook 通知事件类型列表和 处理最佳实践。 本页是边缘情况的契约——当投递没有按主路径所示行为时 出现的问题。事后检查某一次投递,见 Webhook 通知事件日志与手动重发; 事件词典和批量重放,见 Webhook 通知事件词典与批量重放。

我的接口有多长时间响应?

**15 秒。**这是投递调用的读超时;3 秒内 未建立的连接会更早失败。超过任一项都算作 投递失败,该记录回到重试计划。 所以不要在请求内做您的业务处理。验证签名、 持久化事件、应答,然后异步处理——这是 最佳实践页面推荐的模式。 一个在应答前调用您自己三个服务的处理器,是写得正确的集成 显示为 FAILED 的最常见原因。

我返回了 HTTP 200,为什么投递仍算失败?

因为状态码不是决定因素。判定读取的是您的 响应体,只有以下两条同时成立时投递才算 成功:
  1. 响应是 2xx——任何 4xx 或 5xx 在读取响应体之前就失败。
  2. 响应体是带 message 字段且其值为 success 的 JSON 对象。
其他一切都算失败:响应体为 failed 或 unknown(两者都表示 “请重试我”)、空响应体、不同的字段名、HTML 错误页,或 完全无法解析为 JSON 的响应体。 三个具体点解释了大部分意外:

响应的 Content-Type 必须是 application/json 吗?

实际上是——请发送 Content-Type: application/json。响应按 JSON 读取,所以以 text/plain 或 text/html 提供的响应体无法解析, 即便字节本身是 {"message":"success"}, 投递也会被记为失败。已发布的 概览正因此在商户 响应上展示了该请求头。

如果签名验证失败,我应该拒绝请求还是照常处理?

绝不处理。先验证,只在验证通过后才对事件 采取行动——这也是一项 验收要求: 验证失败时不得更新本地状态,必须改为从查询 API 读取真实 状态。 该返回什么由此得出:应答 {"message":"failed"},这让 记录留在重试计划中,然后去查明原因。无法 验证的签名是某一方的配置问题,不会 因为重试而自行修复——所以把重试当作缓冲时间,而不是修复手段, 同时通过查询 API 对账。 如果您的签名验证只在重新序列化报文后才失败, 那就是原因:请对原始请求体字节验证,绝不要对 解析后再字符串化的副本验证。字段顺序、空白和数字格式在 一次往返后都会改变,而签名是对原始 字节计算的。见 Webhook 通知签名验证。

“8 次重试”到底是什么意思?间隔有保证吗?

包括第一次在内共八次尝试,间隔逐渐增大——计划见 Webhook 通知重试,端到端约 20 小时。 计划本身不会告诉您的两点限定:
  • **间隔是最小值,不是约定时刻。**重试在其计划时间变为可执行, 并在投递工作进程下次拾取时发出。 积压时某次尝试可能比表格显示的更晚到达。永远不要编写 假定重试在精确时间到达的逻辑。
  • 预算用尽后记录最终失败,不再有进一步 自动尝试。Portal 显示为没有下次重试时间的 FAILED 记录。

为什么”下次重试时间”有时是空的?

因为它只在自动重试确实仍在计划中时才填充。 失败且还有重试预算的记录会填充该字段; 已投递的记录和重试耗尽的记录为空。 因此两列的组合才是真正的解读,而不是单看状态:

所有尝试都失败后,我该怎么做?

从该对象自己的查询 API 读取当前状态并对账—— 权威状态始终在对象上,不在通知中。 故障恢复指引列出了同样的 三个步骤:查询对象、定期批量对账、如果投递从未到达 则联系支持团队。 修好接口后您也可以从 Portal 重放,支持单条、 多选或按日期范围模式,每次请求最多 50 条。注意对 仍然有故障的接口重放会消耗同一预算中的一次尝试—— 所以先修复,再重放。详情见 Webhook 通知事件词典与批量重放。

为什么某条记录上的 Resend 是灰的?

记录无法重发恰好有三个原因: 注意不在该列表上的:已成功投递的记录 可以重发。这是有意的——Waffo 记为已接受的投递 在您这边可能仍未处理,所以允许重放。 重发还需要您 Portal 角色具有Webhook 通知重发权限。没有 该权限时,操作和行选择根本不会渲染,而不是显示为 禁用——所以完全看不到重发控件的操作人面对的是 权限问题,不是记录资格问题。请联系您的 Portal Admin。

每种事件的回调 URL 来自哪里?

这是”没有收到通知”的最常见单一原因:没有解析出 URL, 所以什么都没发,也没有写任何记录。 两个值得据此设计的后果:
  • **没有全局支付通知 URL。**支付通知只发往 您在创建订单时传的 notifyUrl,该字段之所以必填, 正是因为没有回退。您不能在 Portal 配置一个地址 然后不再发送该参数。
  • **拒付通知没有请求级参数。**如果 Portal URL 为空,这些事件就不会投递,也不留下日志记录。 Webhook 通知日志页上拒付结果为空,几乎总是意味着 Chargeback Notify URL 从未配置。支付过程中绑卡的 绑卡通知同理;没有 notifyUrl 的独立绑卡 请求同样不会被通知。
三个 Portal 侧 URL 的配置见 Portal 自助安全配置。

我改了通知 URL,已在重试队列中的通知会发到哪里?

**发到旧 URL。**回调 URL 在每条通知记录创建时 存储在该记录上,每次重试——以及每次手动重发——都发到 存储的值。编辑 Portal 配置,或在新订单上传入不同的 notifyUrl,只影响之后创建的记录。 请相应地规划 URL 迁移:保持旧接口继续接受流量,直到 在途窗口耗尽,而不是在新 URL 保存的那一刻 就切换。

通知 URL 字段接受什么?必须是 HTTPS 吗?

保存时的校验接受:
  • 以 http:// 或 https:// 开头的 URL
  • 仅 ASCII 字符,且无空白
  • 带主机的可解析地址——端口、路径和查询字符串 都允许
  • Refund 和 Chargeback Notify URL 最多 512 个字符
  • 空值,即清除配置
无论如何请用 HTTPS。http:// 能通过校验,但已发布的 配置要求规定 HTTPS 且 TLS 1.2 或以上,并且Webhook 通知携带支付数据。 一个从校验规则看不出来的硬约束:**您的 TLS 证书必须被公共证书颁发机构信任。**投递 客户端使用默认信任库且没有例外路径,所以自签名或 其他不受信任的证书会导致连接失败,每次尝试都失败。 私有 CA 在这里不可用。

同一事件会到达多次吗?我应该按什么去重?

会,重复是可能的,您的处理器必须幂等—— 验收标准正是测试这一点。重复的来源包括 内部重投、手动重发,以及同一订单上授权阶段的通知 后跟最终通知。 **报文中没有事件 id 或投递 id。**报文体是 {"eventType": ..., "result": {...}},唯一值得注意的请求头是 签名。所以请按 eventType 加 result 内的业务标识符去重—— 事件所涉及的支付订单、退款订单、订阅、Payout 订单、 提现订单或 token——再结合您正在 处理的状态。仅按标识符去重会让您丢掉 已记录授权的订单的最终状态。

到达顺序有保证吗?我会先收到”failed”再收到”succeeded”吗?

不,顺序没有保证,而且是的——您可能乱序收到通知。 重试、排队和网络延迟都会重排投递。 让这一点安全的规则在 最佳实践页面:**不要用 到达顺序驱动您的状态机。**把每条通知当作触发器, 从对象获取终态——要么从报文的状态字段 并加上防止回退的保护,要么在任何通知后调用查询 API。 对于订阅这是文档化的模式:在状态通知或周期变更通知之后, 调用一次订阅查询,并对结果运行 单一的统一流程。

先授权后请款的订单,在授权阶段我会收到通知吗?

会,它以同一订单上的 PAYMENT_NOTIFICATION 到达,与最终 通知相同——不是独立的事件类型。您通过报文中的订单状态 区分它们,而不是通过事件: 这也是为什么仅按订单 id 去重是错的:这里一个订单收到 两条通知是预期的,且只有一条是终态。见 授权与请款。

通知会携带非终态状态吗?我需要重新查询吗?

会——如上表所示,订单可能在仍处理中时被通知。 对终态状态采取行动;对非终态状态,记录并等待。 如果您当时需要确定性,请调用查询 API 而不是推断。 唯一绝不能让您自行关闭订单的状态是未知 结果:遇到 E0001 时,保持订单开放,用相同参数重试,并等待 查询结果或通知。自行关闭是文档化的 验收失败——见 集成验收标准。

订单被取消、超时或失败时我会收到通知吗?

会——三者都产生 PAYMENT_NOTIFICATION,且三者都落在同一个 终态:ORDER_CLOSE。没有单独的 CANCELLED、EXPIRED 或 FAILED 状态值。 要区分它们,请读取报文中 orderFailedReason 携带的失败详情, 它包含 orderFailedCode 和 orderFailedDescription。所以正确的处理形态是:ORDER_CLOSE 表示 “不要履约”;orderFailedReason 告诉您对客户说什么以及 重试是否有意义。

我需要订阅事件吗,还是所有事件都会自动触发?

没有订阅注册表,也没有按事件的选择加入。只要按上表 能为某事件解析出回调 URL,它就会被投递——这意味着您 对接收内容的控制恰好等于您对哪些 URL 存在的控制。 对最小集成的实际解读:在创建订单时传 notifyUrl 并处理支付通知;对您尚未编写处理器的事件族, 把 Portal 配置的 URL 留空,改从 查询 API 获取那些状态。轮询对每一种您能枚举的对象都可行: 拒付通过 /api/v1/chargeback/list 和 /api/v1/chargeback/inquiry, token 状态通过 /api/v1/tokenization/inquiry。代价是延迟——而 对拒付而言,举证截止期从案件创建起算,所以如果您处理 争议,请配置 Chargeback Notify URL 而不是依赖 轮询。

我能在 Portal Webhook 通知页面找到 Payout 和提现通知吗?

通过该页面不能。Webhook 通知日志页搜索固定的七个事件 族——支付、退款、绑卡、拒付和三个订阅 事件——Payout 和提现通知不在其中,所以无论怎么筛选都 不会出现。 对于这些,请从 Payout 和提现查询 API 读取状态。并注意 对应的配置不对称:两者都没有 Portal 配置的 URL,所以其 通知地址就是您在创建它们的请求上传的值。

我怎么知道某条通知从未投递成功?

目前是靠自己看,而不是被告知。投递失败时没有面向商户的告警, 也没有查询通知记录的商户 API。您的 两条路径是:
  1. Portal Webhook 通知日志,在其 90 天窗口内——筛选失败, 并用下次重试列区分”仍在重试”与”最终 失败”。
  2. **对照查询 API 对账。**定期查询您期待终态的对象, 并与您自己的记录比较。 这是推荐的安全网,也是唯一同时覆盖 Portal 页面不跟踪的事件的手段。
请建好第二条。它能捕获第一条无法展示的情形: 因为没有解析出 URL 而从未被创建的事件。

你们从固定的 IP 地址集合投递吗?我能加白名单吗?

没有公布的Webhook 通知投递出口 IP 段,也没有配置静态出口 地址——所以没有什么可以固定。按 IP 为 Waffo 的 回调来源加白名单不是可以据此设计的做法。 替代做法:认证回调本身而不是其来源。每条 Webhook 通知都携带对原始报文体的签名,用 Waffo 私钥签署, 可用商户 Portal → Integration 中的 Waffo 公钥验证。 经过验证的签名比来源 IP 匹配是更强的保证,且 不会因基础设施变化而失效。

开发时我怎么在本机接收回调?

您的 notifyUrl 必须能从公网访问——localhost 和 私有地址无法访问。请使用隧道工具,并把隧道的公网 URL 注册为您的 notifyUrl;已发布的 概览展示了两种常见 工具的命令。 两个实际问题:隧道主机名通常每次重启都会变化, 所以请按订单传 URL 而不是保存在 Portal 配置中,且 所呈现的证书必须被公共信任——托管隧道 能提供,而本地自签名服务器不能。
Sandbox 投递遵循同一契约,包括重试计划——sandbox 首先能 做到和不能做到什么,见 Sandbox 模拟限制 FAQ。