当前位置:首页 > 文章列表 > 科技周边 > 人工智能 > OpenAI Responses API Webhook 怎么验签:原始请求体、时间窗口与 response.completed 处理

OpenAI Responses API Webhook 怎么验签:原始请求体、时间窗口与 response.completed 处理

来源:17golang原创 2026-08-16 15:39:15 0浏览 收藏
所属专题:OpenAI Responses API 工程化实践专题 - 从首个请求、工具调用到后台任务与生产治理

把 OpenAI Responses API 的长任务放到后台后,服务端一般没必要一直占着连接做轮询。Webhook 可以把 response.completed、失败或取消事件推回业务接口,但回调一到就直接更新订单、发通知,风险也跟着来了:请求体可能被中间件改写,重复投递可能让同一任务被重复执行两次,没做验签的请求绝对不能直接进入业务逻辑分支。

可靠的处理顺序是:先读取原始请求体,再用 OpenAI SDK 验证签名,验证通过后解析事件;随后用事件 ID 或响应 ID 做幂等判断,最后才查询 Responses API 并更新业务状态。

要点速览

  • request.data 或原始字节必须先保存,不能先解析 JSON 再验签。
  • 官方 SDK 的 webhooks.unwrap() 会同时完成签名校验和事件解析。
  • 签名头包含事件标识与时间信息,默认时间容忍窗口为 5 分钟,服务端时钟要保证可靠。
  • response.completed 只说明响应生成完成,业务结果仍应按 data.id 查询并幂等落库。

先把回调链路拆成三段

一条可维护的链路至少分成三个部分:入口层接收 HTTP body,安全层验证签名,业务层根据事件类型处理结果。入口层不要把 body 交给会自动格式化 JSON 的中间件之后就丢掉原文,因为签名校验针对的是接收到的原始内容。

OpenAI 的 Webhook 事件使用 webhook-id 标识一次投递,webhook-timestamp 表示投递时间,webhook-signature 携带签名。事件类型则放在 JSON 的 type 字段中;后台 Responses 完成时,常见类型是 response.completed。

OpenAI Webhook 原始请求体经过签名校验后再解析为 response.completed 事件的流程图

Python 中保留原始 body 再验签

下面用 Flask 写一个最小入口示例。request.get_data() 取到的是请求到达时的原始字节;先把它交给 client.webhooks.unwrap(),成功后 SDK 才会返回已解析的事件对象。

import os
from flask import Flask, request
from openai import OpenAI

app = Flask(__name__)
client = OpenAI()
secret = os.environ["OPENAI_WEBHOOK_SECRET"]

@app.post("/hooks/openai")
def receive_openai_hook():
    raw_body = request.get_data()
    try:
        event = client.webhooks.unwrap(
            raw_body,
            request.headers,
            secret=secret,
        )
    except Exception:
        return {"message": "invalid signature"}, 400

    if event.type == "response.completed":
        response_id = event.data.id
        # 这里交给幂等队列,避免在 HTTP 请求中做长事务
        print("verified response", response_id)
    elif event.type in {"response.failed", "response.cancelled"}:
        print("verified non-success event", event.type)
    else:
        print("ignored event", event.type)

    return {"ok": True}, 200

如果希望把验证和解析拆开,可以先调用 verify_signature(),再对同一份原始 body 做 JSON 解析。两种写法都遵循同一个原则:原始 body 不能在验签前被重新序列化。

时间窗口不是装饰:它决定重放边界

签名正确不代表请求永远有效。官方 SDK 会检查时间戳,默认容忍窗口是 300 秒;超过窗口的旧请求应直接拒绝,服务器时间明显超前也会导致校验失败。容器里的时钟漂移、代理缓存回放、手工重放旧 body,都会在这个环节被拦截。

排查时可以按这个顺序走:

  • 记录 webhook-id、接收时间和 HTTP 状态,不要记录签名密钥。
  • 比对应用节点与可信时间源的差异,重点排查跨可用区节点。
  • 确认反向代理没有缓存 POST 请求,也没有把旧请求重新投递到业务入口。
  • 需要人工补偿时重新发起业务查询,不要把历史 body 原样当成新回调处理。

response.completed 到业务落库还差一步

收到 response.completed 后,先用 webhook-id 做投递级幂等,再用 event.data.id 做响应级幂等。前者防范同一条 Webhook 重复到达,后者防范业务重试、补偿任务和多次通知共同处理同一个 Responses 响应。

response.completed 事件按 webhook-id 和 response id 去重后查询结果并更新业务状态的流程图
def handle_completed(event, store, responses_client):
    delivery_key = event.id
    response_id = event.data.id

    if store.has_delivery(delivery_key):
        return "duplicate delivery"
    store.save_delivery(delivery_key, response_id)

    if store.has_response(response_id):
        return "already applied"

    response = responses_client.responses.retrieve(response_id)
    store.save_response(
        response_id=response.id,
        status=response.status,
        output_text=response.output_text,
    )
    return "applied"

这里把查询结果落库放在幂等记录之后,实际项目还需要事务或唯一索引兜底。如果处理过程可能超过网关超时,入口只负责验签并写入内部队列,消费者再完成查询和业务更新。

四个容易误判的失败现场

现象优先检查处理方向
验签全部失败body 是否被解析后重组改为读取原始字节
偶发时间戳过期节点时钟、代理缓存修正时间同步并禁用 POST 缓存
同一响应落库两次是否只按 HTTP 请求次数判断给 webhook-id 与 response id 建唯一约束
收到完成事件但查不到结果查询时机与租户配置进入短暂重试队列,保留原事件

常见问题

Webhook 验签必须自己实现 HMAC 吗?

不必优先自己拼接签名算法。Python、Node.js、Go 等官方 SDK 都提供了验证入口;只有在特殊运行环境下,才考虑使用 Standard Webhooks 兼容库,并严格保留原始 body 和三个签名相关请求头。

为什么先 json.loads 再验签会失败?

JSON 重新序列化可能改变空格、转义或字段顺序,得到的字节串就不再是服务端签名时的原文。应先验证收到的原始字符串或字节,再做解析。

response.completed 可以直接当成业务成功吗?

不能。它表示 Responses 响应完成,业务还要读取响应状态和输出内容,并根据自身规则判断是否可以更新订单、消息或任务记录。

重复 Webhook 要返回什么状态?

只要签名有效,重复事件可以快速返回成功,避免发送方持续重试;真正的业务去重由数据库唯一约束或幂等存储完成。

把安全门槛放在业务分支之前

Webhook 入口最值得坚持的顺序只有一句话:原始 body 保留好,签名先验,事件再解析,结果按两个 ID 幂等处理。这样即使回调重复、处理进程重启,或者一次查询暂时失败,也能把补偿范围限制在一条可追踪的响应记录内。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Gemini Files API 怎么管理上传文件:ACTIVE 状态、48 小时过期与主动删除Gemini Files API 怎么管理上传文件:ACTIVE 状态、48 小时过期与主动删除
上一篇
Gemini Files API 怎么管理上传文件:ACTIVE 状态、48 小时过期与主动删除
MCP 工具注解怎么做安全验收:readOnlyHint、destructiveHint 与幂等边界
下一篇
MCP 工具注解怎么做安全验收:readOnlyHint、destructiveHint 与幂等边界
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    280次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    334次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    329次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    299次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    120次使用