重复的验证 Webhook:授予任何权限之前先去重

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

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

重复的验证 Webhook:授予任何权限之前先去重

一个公开问题询问如何避免 Laravel 应用重复处理同一个 Webhook 事件。我们看不到那个应用,不代表提问者,也不声称修复任何平台。不过,身份验证入驻中的相邻问题是具体的:服务商重试回调,你的处理程序执行了两次,于是权限被授予两次,或者后来的更正被覆盖。你需要决定去重保证放在哪里:队列、缓存,还是与修改验证状态同一个数据库事务。本文用经过检查的本地 Python fixture 展示一种做法及其局限。该 fixture 不是 Laravel 代码,其权限标记也不是真实的业务权限。

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

接入指南

问题与决策

重试是 Webhook 的正常行为。发送方超时或收到意外响应时,可能再次投递同一事件。如果处理程序先在一步中记录事件、再在另一步中修改状态,两个并发投递可能都通过检查。你的应用需要决定:重复保护是与状态变更一起原子执行,还是只是尽力而为。下文主张采用原子方式,并在本地进行测试。

fixture 实际实现了什么

可下载的 verification_delivery 示例把会话和事件存储在 SQLite 中。events 表以 (session, event) 为主键,因此同一事件 ID 在每个会话中只能记录一次。事件处理程序会开启 BEGIN IMMEDIATE 事务。它检查 tenant 和 subject 是否与会话一致,不一致时返回 BINDING_MISMATCH。如果事件 ID 已存在,就原样返回当前状态;否则在同一事务中记录事件并更新状态。 处理程序只接受合成输入:事件 ID 必须以 fixture- 开头,mode 必须为 synthetic_fixture,source 必须为 fixture_callback。其他任何输入都会引发 PRODUCTION_SIGNATURE_VERIFIER_NOT_IMPLEMENTED。它不验证任何真实签名。

假设场景与预期输出

假设一个卖家入驻流程创建了 fixture-session,deadline 为 60。随后事件 fixture-approved 到达,状态为 Approved,并显式设置 policy=True。预期的批准输出为:status approved、deadline 60、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 调用 recover,返回:status expired、permission_granted false、grant_count 0、next review_or_authorized_provider_refresh、provider_polled false。

仍由你负责的失败处理

未知状态会变为 unknown,绝不会被当作批准。会话一旦处于 approved、declined 或 expired,唯一接受的变更是 Approved 之后收到 Declined;其他任何事件都不会改变它。 针对不存在会话的事件返回 BINDING_MISMATCH;读取不存在的会话返回 SESSION_NOT_FOUND。recover 只在本地使会话过期,不会轮询任何服务商。 在拟议的 Laravel 设计中,你需要自行选择对应机制。一种做法是对服务商事件 ID 加会话建立唯一约束,并与状态更新写入同一个数据库事务。在写入之前,你还需要针对具体服务商的签名验证、快速确认响应,以及通过授权路径进行的有限状态核对。fixture 并未展示其中任何一项,这些也都不是经过测试的 Laravel 行为。

拟议设计与已实现行为

fixture 已实现:幂等的合成事件、绑定检查、单次本地授予、后续 Declined 时撤销,以及本地过期。 仍属提议:真实签名检查、队列语义、对服务商的重试,以及把本地授予映射为真实卖家权限。Approved 不是无条件权限。合成的 permission_granted 值只是 fixture 记录,不是真实的业务权限。

验收清单

在采用这一模式之前,请逐项确认: 1. 重复事件返回未变化的状态,且 grant_count 为 1。 2. 去重与状态变更在同一事务中提交。 3. tenant 或 subject 不匹配时会被拒绝。 4. 后续的 Declined 会清除授予。 5. 未知状态绝不授予任何权限。 6. 错过截止时间会得到 expired,且 provider_polled 为 false。 7. 使用同一 SQLite 数据库重启后状态得以保留。 8. 真实签名验证已被列为待完成的要求。 请先运行指南中的 unittest 命令,再决定沙盒申请是否适合你的任务。

运行已检查的模拟示例

下载并解压 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 辅助写作,并经过独立审稿。

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

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

接入你的应用

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

连接免费沙盒账号

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