Label Studio webhook 任务完成回调的签名处理
Label Studio webhook 的签名处理要先区分原生能力与自建协议:普通 webhook 可配置认证请求头,但不能把静态头值当作逐次计算的正文签名。若接收系统要求 HMAC,可在确认来源身份的自建中继上计算签名,再由业务接收端校验。本文中的签名头均为自定义协议,不是 Label Studio 内置字段。
官方文档地址:https://labelstud.io/guide/webhooks.html
为什么回调到了,接收端仍在报缺少签名
以一个标注结果接入服务为例:接收代码要求每次请求携带动态摘要,而 Label Studio 仅配置了回调地址。此时先核对双方协议,不能直接假定存在 X-Hub-Signature 一类头。官方普通 webhook 配置允许添加自定义 header;公开发送实现也展示了配置 headers 与 JSON 正文的发送方式,没有本文中时间戳加正文的 HMAC 生成逻辑。这一判断针对普通 webhook,不代替定制部署或其他版本的接口约定。
静态认证头适合让入口识别持有共享凭据的发送方,应通过 HTTPS 传输并限制访问;它不随正文变化,也不独自阻止已捕获请求重放。不要在配置页预填一个固定的“签名”字符串,然后期待正文变化时它自动更新。
先把“任务完成”对应到真正的业务事件
常见接入点是 ANNOTATION_CREATED。官方事件参考将它定义为任务新增标注时发送的事件,载荷含 annotation.id、annotation.task、annotation.was_cancelled 等信息。业务上需要多少份标注、是否审阅通过、是否允许跳过,应由项目规则判断;不能把收到一次创建事件直接解释成任务全部完成。
因此入口需保留 action、项目标识与标注标识,再由后台判断是否满足业务完成条件。was_cancelled 为 true 的跳过标注不应未经判断进入“有效完成”分支。若仅发送 action 而关闭完整 payload,接收端也就没有足够字段执行这些判断。
需要动态签名时,明确中继的信任边界
可采用两个独立边界:Label Studio 到中继使用 HTTPS 与专用静态认证头;中继认证成功后,以保留的原始正文生成 HMAC,再发送给业务接收端。中继必须拒绝未认证来源,覆盖而不是透传外部传入的签名头,并禁止绕过它直达业务端。中继签名证明的是中继认可并发送了这些字节;若源凭据泄露,签名无法恢复源身份的可信性。

下面定义一个原创的专用路由协议:只接受 POST /hooks/label-studio,不接收查询参数;签名消息包含协议版本、固定路由、十进制秒级时间戳、随机 nonce 和原始正文。中继使用独立随机密钥,双方从受控配置加载相同的密钥字节,不复用 Label Studio API token。
| 自定义请求头 | 含义 |
|---|---|
| X-Relay-Time | 发送时间,秒级十进制字符串 |
| X-Relay-Nonce | 每次投递新建的32位小写十六进制随机值 |
| X-Relay-Signature | sha256= 前缀加64位小写十六进制摘要 |
这些名字只属于本例。中继可用 secrets.token_hex(16) 创建 nonce;接收端从真实请求读取头和原始 bytes。JSON 解析后重新序列化可能改变空白或编码,必须先针对实际传输的 bytes 验签,再解析业务内容。
把签名计算与防重放存储分开实现
以下函数只演示消息格式、时间窗与摘要比较,没有启动 HTTP 服务。调用方必须在读取正文时限制大小、拒绝重复协议头,并确认真实方法、路径和无查询参数;不能对其他路由复用这段固定路由消息。
import hashlib
import hmac
import re
def message(raw_body: bytes, timestamp: str, nonce: str) -> bytes:
# 固定协议版本和接收路由,末尾正文保持原始字节。
prefix = f"v1\nPOST\n/hooks/label-studio\n{timestamp}\n{nonce}\n"
return prefix.encode("ascii") + raw_body
def make_signature(key: bytes, raw_body: bytes,
timestamp: str, nonce: str) -> str:
# key来自受控配置;发送端转发的正文必须就是raw_body。
digest = hmac.new(
key, message(raw_body, timestamp, nonce), hashlib.sha256
).hexdigest()
return "sha256=" + digest
def valid_signature(key: bytes, raw_body: bytes, timestamp: str,
nonce: str, supplied: str, now: int) -> bool:
# 先限制格式,避免非ASCII头值、巨大整数或错误摘要格式。
if not re.fullmatch(r"[0-9]{1,12}", timestamp):
return False
if not re.fullmatch(r"[0-9a-f]{32}", nonce):
return False
if not re.fullmatch(r"sha256=[0-9a-f]{64}", supplied):
return False
if abs(now - int(timestamp)) > 300:
return False
expected = make_signature(key, raw_body, timestamp, nonce)
# 使用恒时比较函数;通过此处还不代表完成防重放。
return hmac.compare_digest(expected, supplied)
300 秒是本例自定的允许时钟偏差,不是产品规定。接收端使用自己读取的当前时间,维护时钟同步。通过摘要校验后,还要在共享持久化存储中原子登记 nonce,禁止多进程分别用普通内存集合做去重。记录至少保留到该时间戳不可能再次被时间窗接受:允许未来300秒时,固定从接收时保留300秒不一定够。
推荐将已验签事件的持久化收件记录与 nonce 唯一性放在同一事务中;提交成功才返回成功响应,异步工作者按业务幂等键处理。若先消耗 nonce,随后落库失败,又把后续重投当成成功重复,可能丢事件。重复投递的响应应依据已持久化状态决定,不能只看 nonce 存在就宣称业务完成。

怎样判断修复方向正确
下面是供接入时采用的预期用例,不是本文已经执行的测试结果。它们针对中继与业务服务自定义协议,不需要把产品改造成原生支持这些头。
| 输入变化 | 预期判断 |
|---|---|
| 正文改一个字节,沿用原摘要 | 摘要不匹配,拒绝 |
| 时间戳超过约定窗口 | 拒绝,不进入业务处理 |
| 同nonce与同事件再次到达 | 查询持久化收件状态,不重复产生副作用 |
| 验签通过但action不在允许集合 | 不执行标注完成处理 |
| 标注创建但未满足项目完成规则 | 保存事件并重新判断任务状态,不提前完成任务 |
接入时容易混淆的三个问题
只在接收端增加 HMAC 代码够吗?不够。发送端必须按同一协议生成动态签名;普通静态 header 配置不会替你计算它。无需动态签名的内网接入,也可按实际需求采用 HTTPS 与来源认证,无须虚构额外机制。
签名成功等于任务已完成吗?不等于。签名回答字节完整性与持钥身份问题;action、项目归属、有效标注和完成规则仍需业务判断。
记录哪些排查信息?记录事件类型、项目和标注标识、接收状态及错误类别;不要把认证头、密钥或含个人信息的完整标注正文写入普通日志。保留可补偿的收件状态,比只记录HTTP状态更有用。
GitHub Actions 可复用工作流的输入边界变化
- 上一篇
- GitHub Actions 可复用工作流的输入边界变化
- 下一篇
- GitHub Copilot 代码审查证据链的工作流调整
-
- 科技周边 · 人工智能 | 1小时前 | mcp · MCP 资源订阅 notifications/resources/updated subscriptions/listen
- MCP 资源订阅变更通知的客户端处理
- 369浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 | mcp · AI工程 · MCP 长任务 progress token 进度通知 智能体协议
- MCP progress token 长任务进度回报的实现
- 367浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- Gemini API 预估 token 与使用量元数据的区分
- 254浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- MLflow signature required 字段的输入收窄
- 202浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- Ray Serve DeploymentHandle 的异步批处理
- 307浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 | 人工智能 · ONNX Runtime IOBinding 设备内存复用
- ONNX Runtime IOBinding 的设备内存复用
- 412浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 | JAX PyTree register_pytree_node register_dataclass jax.jit
- JAX PyTree 自定义数据结构的注册方法
- 156浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- PyTorch DistributedSampler 每轮 shuffle 的复现配置
- 241浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 | 文本对齐 Transformers tokenizer offset_mapping Fast tokenizer
- Transformers tokenizer offset mapping 的原文区间对齐
- 423浏览 收藏
-
- 科技周边 · 人工智能 | 1小时前 |
- Hugging Face Datasets unique 怎样列出字段的不同取值
- 469浏览 收藏
-
- 科技周边 · 人工智能 | 19小时前 |
- ONNX Runtime SessionOptions 怎样控制推理会话线程数
- 351浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 754次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 752次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 833次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 798次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 633次使用
-
- 量化模型本地推理的显存估算方法
- 2026-10-17 501浏览
-
- 本地大模型反复输出同一句话怎么调整生成参数
- 2026-09-06 501浏览
-
- Python 调用大模型时如何用结构化输出校验 JSON:从解析失败到可重试
- 2026-08-29 501浏览
-
- AI写作工具免费版安装教程(含豆包Clawdbot)
- 2026-05-30 501浏览
-
- WPS AI能自动生成PPT吗?输入主题一键制作演示文稿
- 2026-05-27 501浏览

