Webhook 返回了 HTTP 302:为什么会收到重试,以及期间验证状态应如何处理

先在免费沙盒中跑通流程,再准备生产接入。

统计选择在本站复用,可随时关闭;账号关联另行同意。

Webhook 返回了 HTTP 302:为什么会收到重试,以及期间验证状态应如何处理

一个公开问题描述了这样的情况:PayPal webhook 监听器返回 HTTP 302,随后不断收到重试。我们看不到该应用,不代表提问者,也不声称修复 PayPal 或任何平台。但在身份引导场景中,相邻的问题是具体的。重定向并不是确认。回调未被确认时,发送方可能重试,也可能该回调根本没有被处理。无论哪种情况,你的引导服务都必须如实记录状态。读者需要做的决定是:当验证回调被弹回、重复到达或始终未到达时,应用应该记录什么,又必须拒绝做什么?本文只用已检查的本地合成示例来回答这个问题,并且全程区分“建议的应用设计”与“fixture 实际实现的行为”。

curl https://gridzen.ai/console/api/quickstart/trial \
  -H 'Content-Type: application/json' \
  -d '{"task": "identity_onboarding", "scenario": "success"}'

接入指南

问题:重定向看似成功,实则不是

许多框架默认会重定向未认证或非规范的请求。常见原因包括登录中间件、HTTP 到 HTTPS 的重写,或末尾斜杠规则。在你的日志里,这个请求看起来已经处理完毕。但对 webhook 发送方来说,302 通常不是它期望的确认,于是它会安排再次投递。由此产生两个风险。第一,路由修复后,同一事件可能多次到达。第二,只要重定向仍然存在,事件可能永远到不了处理程序,会话就会一直停留在 pending。以下是建议设计,并非 fixture 的行为:将回调路由排除在登录和重定向中间件之外;只在事件被持久保存之后,才直接返回成功状态;其他任何结果都视为未送达。fixture 没有 HTTP 监听器,也没有重定向逻辑,因此无法演示这一部分。它展示的是事件到达或未到达之后,状态如何变化。

fixture 实现了什么

Identity 类用 SQLite 持久保存会话和事件。create 步骤保存一个带截止时间的 pending 会话,并返回一条模拟记录。这条记录中,hosted_link 指向 example.invalid 地址,link_executable 为 false,next 为 send_real_provider_link_only_after_authorization。event 步骤只接受 mode 为 synthetic_fixture、source 为 fixture_callback 的输入。其他任何输入都会抛出 PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED,因此它不做真实签名验证。事件标识必须以 fixture- 开头。event 步骤还会校验已保存的 tenant 和 subject:不匹配或会话未知时抛出 BINDING_MISMATCH,而读取未知会话时抛出 SESSION_NOT_FOUND。每个事件以“会话加事件”作为主键保存,因此重复到达的事件会原样返回当前状态。recover 步骤会把已过截止时间的 pending、review 或 unknown 会话标记为 expired。它只在本地完成这一操作,并返回 provider_polled false。

假设示例:一次被重试的批准,随后是沉默

以下为假设场景。重定向缺陷被修复后,发送方排队的重试陆续到达。在 fixture 中,会话在时间 0 创建,timeout 为 60,因此 deadline 为 60。事件 fixture-approved 到达,status 为 Approved,policy 为 true。经测试的输出为:status approved、permission_granted true、grant_count 1、live_permission_changed false、policy_version 1、allow_action true。随后同一事件作为重试再次到达,输出完全相同,grant_count 仍为 1。之后一次 Declined 更正得到 status declined、permission_granted false、grant_count 1。再看另一条路径:回调始终没有到达。在时间 61 对一个新的 pending 会话调用 recover,返回 status expired、permission_granted false、grant_count 0、next review_or_authorized_provider_refresh、provider_polled false。沉默从未变成批准。

由你的应用负责的失败处理

以下为建议设计。将回调路由上的所有非 2xx 响应和重定向响应记录为投递缺陷,而不是验证结果。先持久保存事件再进行确认,这样重试就是无害的。按计划依据已保存的截止时间运行恢复。过期后,把该案例转入人工审核,或向你的提供方发起经授权的状态刷新;fixture 不执行这种刷新。把 BINDING_MISMATCH 和 SESSION_NOT_FOUND 视为需要调查的拒绝,绝不能以此为由新建会话。fixture 中的 permission_granted 只是本地演示标志。任何真实权限都需要你自己的集成、当前证据和明确的策略。

本示例的限制

fixture 只接受合成回调。它不验证真实提供方的签名,不轮询任何提供方,也不复现 HTTP 重定向。它的授权记录属于 fixture 数据,因此合成权限不是真实的业务权限。测试覆盖了模拟状态转换、绑定、重复回调、重启和超时,但不能证明生产环境中的成功。托管身份试用是一条独立且有限制的通道,仅面向符合条件的 GB/US 企业。针对新获批准的真实用户的端到端操作,目前仍未验证。

验收清单

在推进沙箱集成之前,请逐项确认:1. 回调路由不返回重定向,也不需要登录。2. 事件在确认之前已保存。3. 重复事件不改变 grant_count。4. 批准之后的 Declined 状态会清除 permission_granted。5. 截止时间之后调用 recover,得到 expired 且 provider_polled 为 false,而不是批准。6. tenant 或 subject 不匹配时抛出 BINDING_MISMATCH。7. 使用同一个 SQLite 数据库重启后,状态依然保留。8. 真实签名验证和经授权的状态刷新需要另行规划。请先运行指南中的重启和超时测试。

运行已检查的模拟示例

下载并解压 https://gridzen.ai/downloads/verification-delivery.zip,进入 verification_delivery,用 Python 3.10+ 运行。此本地示例不联网;独立身份实验请使用新的数据库。

python3 delivery.py identity --db /tmp/gridzen-article-fixture.sqlite

来源与范围

https://gridzen.ai/guides/missing-verification-webhook/ https://gridzen.ai/guides/current-capabilities/ 依据已检查的来源快照与本地模拟;建议的业务规则属于设计建议,不代表已证明的生产行为。内容使用 AI 辅助写作,并经过独立审稿。

另行体验三种沙盒结果 创建账号,继续项目接入

下载不等于运行成功;本地示例与在线沙盒分别测试。

接入你的应用

运行三种模拟结果 · 需要接入帮助

连接免费沙盒账号

号码基础有效性不证明持有权或送达,不提供短信验证码。业务放行由你的系统判断。