> ## Documentation Index
> Fetch the complete documentation index at: https://waffo.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook 通知事件字典与批量重发

> 商户 Portal 跟踪的七种Webhook 通知事件类型、每种事件携带哪个标识以便你匹配回自己的订单、日志保留多久，以及如何用 Retry by Date 重放一段时间窗口内的失败投递。

三个页面涵盖Webhook 通知，它们回答不同的问题：

| 问题 | 页面 |
| - | - |
| 投递契约是什么、我的端点必须返回什么、自动重试如何工作？ | [Webhook 通知概览](/docs/zh/developer-docs/webhook/overview)和[Webhook 通知重试](/docs/zh/developer-docs/webhook/retry) |
| 每个事件的载荷里有什么？ | [Webhook 通知事件类型列表](/docs/zh/developer-docs/webhook/event-types) |
| 我如何在 Portal 中检查一次投递、为什么某条记录不存在、为什么 Resend 是灰色的？ | [Webhook 通知事件日志与手动重发](/docs/zh/support/portal-webhook-log-faq) |

**本页是字典和批量工具**：哪些事件作为 Portal 可跟踪的
事件存在、每种事件携带哪个标识以便你匹配到自己的记录、日志
能追溯多远，以及如何一次重放整个时间窗口内的失败。

## Portal 跟踪哪些事件类型？

七种。这是 **Event Type** 筛选项提供的确切集合，也是你
留空该筛选项时搜索的集合：

| `eventType` | Portal 中的标签 | 触发时机 |
| - | - | - |
| `PAYMENT_NOTIFICATION` | Payment Notify | 一笔支付完成或失败 |
| `REFUND_NOTIFICATION` | Refund Notify | 一笔退款完成或失败 |
| `TOKENIZATION_NOTIFICATION` | Tokenization Notify | 生成了 token、其状态变化，或其卡数据更新 |
| `SUBSCRIPTION_STATUS_NOTIFICATION` | Subscription Status Notify | 订阅自身的状态变化——激活、取消、关闭 |
| `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` | Subscription Period Notify | 一个账单周期到达终态 |
| `SUBSCRIPTION_CHANGE_NOTIFICATION` | Subscription Change Notify | 订阅升级或降级完成 |
| `CHARGEBACK_NOTIFICATION` | Chargeback Notify | 创建了拒付案件或其状态变化 |

两点能省去无谓的搜索：

* **拒付事件只有一种。** 每次拒付创建和案件上的每次状态变化
  都以 `CHARGEBACK_NOTIFICATION` 到达。没有单独的预警事件，也没有
  单独的拒付退款事件可以订阅。
* **`eventType` 列中的值就是上面的值**，所以你在 Portal 中读到的
  就是你在自己代码中匹配的。友好标签仅用于显示。

## 我如何把一条Webhook 通知匹配回自己的订单？

每条记录携带一个 **Order ID**，它是哪个标识取决于事件。这
是对照表：

| 事件 | 日志中的 Order ID | 你自己的匹配键 |
| - | - | - |
| `PAYMENT_NOTIFICATION` | `acquiringOrderId`——Waffo 支付订单 | 你的 `merchantOrderId`，在载荷中携带 |
| `REFUND_NOTIFICATION` | `acquiringRefundOrderId`——Waffo 退款订单 | 你退款的商户订单 ID，加上它所退款的原订单 |
| `TOKENIZATION_NOTIFICATION` | `tokenId` | 你为顾客存储的 token |
| `SUBSCRIPTION_STATUS_NOTIFICATION` | Waffo 订阅 ID | 你的 `merchantSubscriptionId`，在载荷中携带 |
| `SUBSCRIPTION_PERIOD_CHANGED_NOTIFICATION` | Waffo 订阅 ID | 同上，加上载荷中的期数 |
| `SUBSCRIPTION_CHANGE_NOTIFICATION` | 变更产生的**新**订阅 ID | 你提交的变更请求，加上载荷中的原订阅 ID |
| `CHARGEBACK_NOTIFICATION` | 拒付通知本身的一个标识 | **载荷内的 `originalOrderId`**——被争议的支付订单 |

拒付这一行是让人意外的，所以说得直白一点：**按你的原支付订单号搜索日志
找不到拒付通知。** 那里的 Order ID
列是案件自身的标识，不是你的订单。打开记录，从请求体中读取
`originalOrderId`。反过来同样要注意——不要把
拒付日志的 Order ID 用作你自己系统中的键。

对于订阅，注意升级或降级会产生一个**新的**订阅 ID，所以
`SUBSCRIPTION_CHANGE_NOTIFICATION` 不会匹配你变更前持有的订阅 ID。
如果你以它为键，请从载荷中读取原 ID。

## 其中哪些是我能订阅的，URL 从哪里来？

只有两个回调 URL 是你在 Portal 中设置的；其余的随创建该对象的 API 调用
一起传递。Webhook 通知日志页面上没有任何东西能改变 URL——该页面按设计
是只读的。

| 事件 | 其 URL 来自哪里 |
| - | - |
| `PAYMENT_NOTIFICATION` | 订单创建请求上的 `notifyUrl` |
| `REFUND_NOTIFICATION` | 退款请求上的 `refundNotifyUrl`；缺失时，为 Portal 中配置的 Refund Notify URL |
| `TOKENIZATION_NOTIFICATION` | token 生成请求上的 `notifyUrl`；Portal 中也可配置 Tokenization Notify URL |
| 订阅事件 | 订阅自身持有的 `notifyUrl` |
| `CHARGEBACK_NOTIFICATION` | 仅 Portal 中配置的 Chargeback Notify URL——不能按请求传递 |

Portal 侧 URL 的配置在
[Portal 自助安全配置](/docs/zh/support/portal-security-config-faq)中说明。如果
某个事件完全没有产生日志记录，通常的原因是没有解析到 URL——参见
[Webhook 通知事件日志与手动重发](/docs/zh/support/portal-webhook-log-faq)。

## Webhook 通知日志能追溯多远？

**90 天。** 页面在横幅中如此说明，日期选择器不允许选择
90 天前更早的日期，Retry by Date 也受同一窗口限制。对于更早的内容，
请通过其查询 API 查询对象本身，而不是寻找通知。

两个相关的边界：

* 日志从该功能上线的时间点开始。更早的通知不会回填，所以
  一笔很老的交易返回空结果是预期之内的，而不是诊断依据。
* 列表默认按 **Submission Time** 排序，最新的在前。

## 我能一次重放整个时间窗口内的失败吗？

可以——**Retry by Date**，它正是为你的端点在某段时间内宕机、
而你希望重新投递该窗口内的失败而无需逐条点击的情形而存在的。

| 输入 | 行为 |
| - | - |
| 时间范围 | 必填。限于最近 90 天；窗口之外的日期不可选择。 |
| 事件类型 | 可选，多选。留空则覆盖全部七种。 |
| 预估 | 对话框会在你更改输入时统计匹配的**失败**记录数，并在你确认前显示该数字。 |

只有失败的记录才符合条件——成功和进行中的记录永远不会包含在内，所以你
不会意外地重新投递你的端点已经接受的内容。

## 一个批次最多能重发多少条？

**每次请求最多 50 条记录。** 这同样适用于 Retry by Date 和多选重发，
并且在两端都强制执行，而不只是 UI 提示。

实际意味着什么：

| 情形 | 发生什么 |
| - | - |
| 预估为 0 | 确认按钮保持禁用——没有什么可发送 |
| 预估为 1–50 | 确认继续 |
| 预估超过 50 | 对话框警告且确认保持禁用：**缩小时间范围或事件类型**，然后确认 |

批次**不会**被静默截断为前 50 条——超限的范围会被直接
拒绝。这是有意为之：被截断的批次会让你以为一个窗口已经
重放，而其实大部分没有。对于大量积压，请分片处理——先按事件
类型分，因为那通常正是沿着你事故实际发生的线路
切分窗口。

## 批量重发后我会得到什么？

按记录的结果，以计数而非单一的通过/失败汇总：

| 结果 | 你看到的 |
| - | - |
| 全部成功 | *n record(s) resent successfully* |
| 全部失败 | *Resend failed for all n record(s). Please try again later.* |
| 混合 | *n resent successfully, m failed.* |

"成功"意味着你的端点在本次尝试中接受了投递——批次报告的是
真实的投递结果，而不仅仅是请求已排队。所以一个返回全部
失败的批次是在告诉你，你的端点仍然不接受载荷；在再次重放之前
先修复它。

在预估和发送之间状态发生变化的记录——例如在此期间通过
自动重试成功了的记录——会计入失败计数而不是被重发，
因为它们已不再符合条件。

## 手动或批量重发会消耗我的自动重试次数吗？

会——在你对一个尚未真正修复的端点重放窗口之前，
这一点值得了解。

重发走的是与自动尝试**相同的投递路径**。如果你的端点
不接受它，该次尝试会像自动尝试一样计入该记录的重试预算，
并且只有在预算仍有剩余时才会安排下一次自动重试。一旦预算
耗尽，记录就最终失败：**Next Retry** 显示 `—`，不会再有
自动尝试。

实际规则：**先验证你的端点，再重放。** 对一个仍然损坏的
端点重放会烧掉本来仍会自行触发的尝试次数。

## Portal 把什么视为"Failed"记录？

`Failed` 既包括已最终失败的记录，也包括本次失败但
仍在自动重试队列中的记录。两者通过 **Next Retry** 列
而不是状态来区分：

| 状态 | Next Retry | 解读 |
| - | - | - |
| Failed | 一个时间戳 | 仍安排了自动重试——你可以不管它 |
| Failed | `—` | 重试已耗尽；只有手动重发能推动它 |
| Successful | `—` | 已投递并被接受 |

这对 Retry by Date 很重要：预估统计的是**两种**失败记录，所以
你重放的窗口可能包含本来会自行恢复的记录。

## 为什么成功的记录不显示我端点的响应？

因为 Portal 对已投递的记录是推导显示内容，而不是存储你的
服务器发送了什么。成功的记录显示 `200` HTTP Response 和 `success` 响应体，因为那
就是"成功"的定义——只有当你的端点返回
HTTP 200 **并且**响应体为 `{"message":"success"}` 时，投递才算成功，依据
[Webhook 通知概览](/docs/zh/developer-docs/webhook/overview)。

真实捕获的细节只存在于**失败**记录上，那里会显示你的端点
返回的失败摘要——或传输错误。所以在你排查时，失败的记录才是
有信息量的；成功的记录背后没有额外隐藏的内容。

## 重发会在日志中创建第二行吗？

不会。**每个业务对象的每个事件只有一条记录**——同一支付订单的支付
通知在其整个生命中是一行。重发会就地更新该行的状态、响应和
尝试次数；不会新增一行。详情视图中的 **Notify Count** 是你的
尝试历史。

这也是为什么你不能用行数来衡量投递量，以及为什么重发过的记录
在后来的尝试覆盖之后会丢失之前的失败摘要。

***

如果你期望的记录是完全缺失而不是失败，请从
[Webhook 通知事件日志与手动重发](/docs/zh/support/portal-webhook-log-faq)开始——缺失的记录
几乎总是意味着没有解析到回调 URL，这是重发无法修复的。


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.