# 静态结果通知

配置账户级静态结果通知，使用独立 Secret 验签，以 eventId 去重并处理重试、乱序、死信和租约恢复。

在账户设置的静态 API 页面配置公网 HTTP 或 HTTPS 端点。HTTP 传输不加密，建议使用 HTTPS。通知 Secret 与账户 API 密钥独立，首次创建时仅展示一次；普通保存不会更换 Secret。通知关闭时，采购、续费、改密仍可通过查询完成。

仅通知 `order.completed`（订单结果），覆盖账户的 API 和控制台来源。服务端固定订阅，无需选择产品或事件；历史配置统一按此范围生效。完成事件携带最终状态与逐项结果，正文不含代理凭据或上游私有信息。

续费等收费操作的新通知还包含 `data.operationId` 和 `data.clientOperationNo`；它们与操作响应一致，操作响应中的 `orderNo` 则对应 `data.orderNo`。采购业务号仍为 `clientOrderNo`，没有关联的字段省略。历史已保存事件保持原正文，重试和后台重投不会补写字段；接收旧事件时可按 `orderNo` 查询订单详情获取关联。

## 验签和去重

通知头为 `X-ZZ-Event-Id`、`X-ZZ-Timestamp`、`X-ZZ-Nonce`、`X-ZZ-Signature`。原文为 `v1`、时间戳、nonce、原始正文 SHA-256 小写十六进制，使用 LF 连接且末尾不加换行。使用通知 Secret 计算 HMAC-SHA256 小写十六进制并以恒定时间比较，校验时间窗口后再解析 JSON。

```json
{"eventId":"a-unique-event-id","type":"order.completed","occurredAt":"2026-09-26T00:00:00Z","data":{"orderNo":"example-order","status":"succeeded","items":[]}}
```

接收端应在业务事务中持久记录 `eventId`，重复请求返回 2xx。通知可能重复，也不保证顺序；收到旧事件时不可把本地新状态回退，必要时调用查询接口获取最新结果。

## 重试和恢复

首次发送失败后，最多自动重试三次，共最多四次投递尝试；重试间隔为 1 分钟、5 分钟、30 分钟，实际执行时间受 Worker 调度影响。每次发送生成新时间戳和 nonce，正文与 `eventId` 不变。领取后进程中断也计入本轮尝试上限；后台人工重投开启新一轮尝试，累计投递历史保留。改密结果由同步接口返回，不产生通知；订单受理、实例交付和到期事件不再对外通知。

只接受 2xx 为送达，不跟随重定向；总超时 5 秒、响应正文最多 64 KiB。失败耗尽进入死信，仅可由后台重投订单结果，重投仍使用原 `eventId`。发送中 Worker 退出后由租约回收；下游已收而本地未确认时可能再次收到同一事件。历史非订单结果投递会被拦截并停止重试，后台也不能重投。

公网目标在连接时验证全部 DNS 解析结果，并直接连接已校验地址；内网、回环、链路本地和受限地址会被拒绝。实现参考 [OWASP SSRF 防护要求](https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html)。

html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}
