1. Cursor 智能体 Webhooks 是什么,能解决哪些回调场景
Cursor 的智能体(Agent)在后台跑任务时,你不可能一直盯着界面刷新。它什么时候跑完、什么时候报错、产出的分支和 PR 在哪,这些状态变化需要一个主动通知机制——这就是 Webhooks 的用武之地。简单说,Webhooks 是 Cursor 在智能体状态发生变更时,向你指定的 URL 发起的一次 HTTP POST 请求,把事件详情推给你。你只要在本地或服务器上跑一个接收端,就能实时拿到这些事件,进而触发后续动作,比如自动发通知、自动跑测试、自动合并分支。
目前 Cursor 的 Webhook 只支持一种事件:statusChange。也就是当智能体进入ERROR或FINISHED状态时触发。别看事件类型少,它覆盖了最关键的节点——任务结束和任务失败。对于做自动化流水线的开发者来说,这两个信号足够驱动大部分后续逻辑。
适合谁用?三类人最需要:一是把 Cursor 智能体接入 CI/CD 的工程师,想在智能体产出 PR 后自动触发流水线;二是做团队协作工具的开发者,想把智能体状态同步到内部 IM 或看板;三是自己写脚本管理多个智能体任务的独立开发者,想用一个统一入口收集所有回调。如果你只是偶尔用 Cursor 写代码,不涉及自动化,那 Webhooks 暂时用不上;但只要你开始让智能体批量跑任务,回调链路就是刚需。
这里有个容易混淆的点:Webhooks 是 Cursor 主动推给你,而不是你去轮询 Cursor 的接口。推的模式延迟低、省资源,但要求你的接收端必须公网可达(生产环境用 HTTPS),并且要能正确处理重试和签名校验。很多新手第一次配 Webhook,接收端返回了 200 但没做签名验证,结果被伪造请求打穿,这是典型的踩坑点。
另外,Webhook 的载荷是 JSON,字段里有event、timestamp、id、status、source、target、summary等。其中source.repository和source.ref告诉你代码仓库和分支,target.prUrl和target.branchName告诉你智能体产出的 PR 和分支,summary是这次变更的简述。部分字段是可选的,只在可用时才出现,所以解析时要做空值判断,不能硬编码假设每个字段都存在。
把 Webhook 接收端跑起来之后,你还需要一个稳定的 API 通道来发起验证请求或做后续的模型调用。TaoToken 在这里扮演的是统一 Key 和 API 通道的角色——你不需要为每个模型或工具单独管理一套密钥,用同一个 Key 就能走通对话、编码、Agent 等场景。下面我会先讲清楚 TaoToken 的接入前置,再给出可复制的接收端配置和签名验证代码,最后用 TaoToken API 发起一次验证请求,把整条链路跑通。
2. TaoToken 统一 Key 与 API 通道接入前置
在写接收端代码之前,先把 TaoToken 的 Key 和 Base URL 准备好。TaoToken 的官网是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api 。注意 API 地址后面不加 UTM 参数,保持干净。
你需要做三件事:注册账号、创建 API Key、确认要用的 Model ID。注册和创建 Key 的入口在控制台,地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。创建好 Key 之后,把它存到环境变量里,不要硬编码进代码。
TaoToken 的接入三件套是:Base URL、API Key、Model ID。Base URL 统一用https://taotoken.net/api,API Key 就是你创建的那串,Model ID 根据你要用的模型填,比如做对话验证可以用通用的对话模型 ID,做编码任务可以用对应的编码模型 ID。这三个值在后面的配置片段里会反复出现,先记牢。
如果你用的是 Claude Code 这类工具,TaoToken 也提供了对应的接入方式。Claude Code 的配置入口在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有详细的 Base URL 和 Key 填写说明。对于 Cursor 智能体开发场景,你主要用 TaoToken 来做两件事:一是用统一 Key 发起验证请求,确认 API 通道畅通;二是在接收端处理完 Webhook 后,调用模型做后续处理,比如自动生成 PR 描述或跑代码审查。
这里要提醒一点:TaoToken 是 API 通道,不是编辑器替代品。Cursor 本身还是你的开发环境,TaoToken 负责的是模型调用和 Key 管理。不要把两者混为一谈,也不要把 TaoToken 写成某种非法中转,它是正规的 API 服务入口。
配置环境变量的时候,建议用.env文件管理,不要提交到 Git。Node.js 项目可以用dotenv,Python 项目可以用python-dotenv。下面是一个.env示例:
TAOTOKEN_API_KEY=sk-你的实际Key TAOTOKEN_BASE_URL=https://taotoken.net/api TAOTOKEN_MODEL_ID=你的模型ID CURSOR_WEBHOOK_SECRET=你的Webhook签名密钥CURSOR_WEBHOOK_SECRET是你在 Cursor 创建 Webhook 时设置的签名密钥,用来做 HMAC-SHA256 校验。这个密钥和 TaoToken 的 API Key 是两回事,不要搞混。Webhook 密钥用于验证请求来自 Cursor,TaoToken Key 用于调用模型 API。
准备好这些之后,就可以开始写接收端了。接收端的核心逻辑是:监听 POST 请求、读取原始请求体、用密钥计算 HMAC-SHA256、和请求头里的签名比对、比对通过后解析 JSON 并处理事件。下面一节给出完整的可复制配置。
3. 可复制的 Webhook 接收端配置与签名验证代码
这一节给出两个版本的接收端:Node.js(Express)和 Python(Flask)。你可以根据自己的技术栈选一个。核心要求只有一个:计算签名时必须用原始请求体(raw body),在任何 JSON 解析之前。这是最容易出错的地方,很多框架默认帮你把 body 解析成对象,导致签名对不上。
先看 Node.js 版本。用 Express 的时候,要用express.raw()中间件拿到原始 Buffer,而不是express.json()。代码结构如下:
const express = require("express"); const crypto = require("crypto"); const app = express(); const WEBHOOK_SECRET = process.env.CURSOR_WEBHOOK_SECRET; // 关键:用 raw 中间件保留原始请求体 app.post("/webhook/cursor", express.raw({ type: "application/json" }), (req, res) => { const signature = req.headers["x-webhook-signature"]; const rawBody = req.body; // Buffer if (!signature || !rawBody) { return res.status(400).send("missing signature or body"); } const expected = "sha256=" + crypto .createHmac("sha256", WEBHOOK_SECRET) .update(rawBody) .digest("hex"); // 用 timingSafeEqual 防时序攻击 const sigBuf = Buffer.from(signature); const expBuf = Buffer.from(expected); if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) { return res.status(401).send("invalid signature"); } // 签名通过后再解析 JSON const payload = JSON.parse(rawBody.toString("utf8")); console.log("event:", payload.event, "status:", payload.status, "id:", payload.id); // 快速返回 2xx,避免 Cursor 重试 res.status(200).send("ok"); // 后续处理放到异步,不阻塞响应 handleEvent(payload).catch(console.error); }); async function handleEvent(payload) { if (payload.status === "FINISHED") { console.log("PR:", payload.target?.prUrl, "branch:", payload.target?.branchName); } else if (payload.status === "ERROR") { console.log("agent failed:", payload.summary); } } app.listen(3000, () => console.log("webhook listening on 3000"));注意几个细节:express.raw({ type: "application/json" })只对application/json生效,确保拿到 Buffer;timingSafeEqual要求两个 Buffer 长度一致,所以先判断长度;响应先返回 200,再异步处理事件,避免处理超时导致 Cursor 重试。
再看 Python 版本。用 Flask 的时候,用request.get_data()拿原始字节,不要用request.json:
import hmac import hashlib import json from flask import Flask, request, abort app = Flask(__name__) WEBHOOK_SECRET = os.environ["CURSOR_WEBHOOK_SECRET"] @app.route("/webhook/cursor", methods=["POST"]) def cursor_webhook(): signature = request.headers.get("X-Webhook-Signature") raw_body = request.get_data() # 原始字节 if not signature or not raw_body: abort(400, "missing signature or body") expected = "sha256=" + hmac.new( WEBHOOK_SECRET.encode(), raw_body, hashlib.sha256 ).hexdigest() if not hmac.compare_digest(signature, expected): abort(401, "invalid signature") payload = json.loads(raw_body.decode("utf-8")) print("event:", payload.get("event"), "status:", payload.get("status")) # 先返回 200 # 实际处理可以放到后台线程或队列 return "ok", 200 if __name__ == "__main__": app.run(port=3000)hmac.compare_digest是 Python 标准库提供的恒定时间比较,比==更安全。request.get_data()返回的是 bytes,直接喂给hmac.new没问题。
如果你用的是 Cursor 的 Claude Code 相关能力,配置里同样要填全三件套:Base URL 用https://taotoken.net/api,Key 用你的 TaoToken API Key,Model ID 填对应模型。Claude Code 的详细配置在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 可以查到。
还有一个常见场景是用 Cline MCP 或 Codex 的auth.json。如果你在 Cursor 里通过 MCP 接入了这些工具,配置里也要写全 Base URL、Key、Model ID 三项。比如 Codex 的auth.json结构大致是:
{ "base_url": "https://taotoken.net/api", "api_key": "sk-你的Key", "model": "你的模型ID" }路径和字段名以你实际使用的工具文档为准,但三件套的逻辑不变。配置写完之后,先本地跑起来,用 curl 模拟一次请求,确认签名校验能通过。下一节给出验证请求的具体命令和成功结果。
4. 验证请求与成功结果:用 TaoToken API 跑通回调链路
接收端跑起来之后,先别急着在 Cursor 里配 Webhook,先用 curl 模拟一次带签名的请求,确认你的签名校验逻辑没问题。这一步能帮你排除掉大部分低级错误。
假设你的接收端跑在http://localhost:3000/webhook/cursor,密钥是test-secret。先用 Node.js 生成一个签名:
BODY='{"event":"statusChange","timestamp":"2024-01-15T10:30:00Z","id":"bc_abc123","status":"FINISHED","source":{"repository":"https://github.com/your-org/your-repo","ref":"main"},"target":{"url":"https://cursor.com/agents?id=bc_abc123","branchName":"cursor/add-readme-1234","prUrl":"https://github.com/your-org/your-repo/pull/1234"},"summary":"添加了包含安装说明的 README.md"}' SIG="sha256=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "test-secret" | awk '{print $2}')" curl -X POST http://localhost:3000/webhook/cursor \ -H "Content-Type: application/json" \ -H "X-Webhook-Signature: $SIG" \ -H "X-Webhook-ID: test-001" \ -H "X-Webhook-Event: statusChange" \ -H "User-Agent: Cursor-Agent-Webhook/1.0" \ -d "$BODY"如果签名正确,接收端会返回ok,控制台打印出event: statusChange status: FINISHED。如果签名错误,返回 401。你可以故意改一个字符再试一次,确认 401 分支生效。
接下来用 TaoToken API 发起一次验证请求,确认 API 通道畅通。这一步的目的是:当 Webhook 事件到达后,你的接收端可能需要调用模型做后续处理,所以先验证 TaoToken 的 Key 和 Base URL 能正常工作。
用 curl 调 TaoToken 的对话接口:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "'"$TAOTOKEN_MODEL_ID"'", "messages": [ {"role": "user", "content": "回复 ok 即可"} ] }'如果返回里有choices字段,说明通道正常。如果返回 401,检查 Key 是否正确;如果返回local proxy failed之类的错误,检查 Base URL 是否写成了https://taotoken.net/api而不是其他地址。
你也可以在 TaoToken 的模型对话页面直接测试,地址是 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,在页面上选模型、输入内容,看是否能正常返回。这一步能快速排除 Key 和模型 ID 的问题。
把 Webhook 接收端和 TaoToken API 都验证通过之后,就可以在 Cursor 里创建带 Webhook URL 的智能体了。创建时填入你的公网 URL(本地开发可以用内网穿透工具,但生产环境必须 HTTPS),设置签名密钥,然后触发一次智能体任务,观察接收端是否收到statusChange事件。
成功的结果是:接收端日志里出现完整的 JSON 载荷,status字段是FINISHED或ERROR,target.prUrl和target.branchName有值,同时你的后续处理逻辑(比如调用 TaoToken API 生成 PR 描述)也能正常执行。如果只收到请求但签名校验失败,回到上一节检查 raw body 的处理方式。
5. 本篇常见错误排查:401、local proxy failed、reading choices、OAuth
这一节列出实际接入中最容易遇到的几类报错,对照排查。
第一类:Webhook 返回 401 invalid signature。原因通常是签名计算用了解析后的 JSON 而不是原始请求体。Express 里如果用了express.json()而不是express.raw(),body 已经被解析成对象,再JSON.stringify回去和原始字节不一致,签名必然对不上。解决方法是改用 raw 中间件,或者用verify回调保存原始 Buffer。Python Flask 里如果用了request.json而不是request.get_data(),同样会出问题。
第二类:TaoToken API 返回 401 Unauthorized。检查三件事:API Key 是否复制完整(有没有多余空格)、请求头是否是Authorization: Bearer <key>、Base URL 是否是https://taotoken.net/api。如果 Key 是在控制台新建的,确认没有过期或被禁用。API Key 管理页在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,可以进去核对。
第三类:返回local proxy failed或类似连接错误。这通常说明 Base URL 写错了,或者本地网络无法访问该地址。确认 Base URL 是https://taotoken.net/api,不要多加路径,也不要用其他域名。如果你在代码里把 Base URL 和具体接口路径拼错了,比如拼成了https://taotoken.net/api/v1/v1/chat/completions,也会报错。正确的对话接口是https://taotoken.net/api/v1/chat/completions。
第四类:返回里没有choices字段,报reading choices之类的错误。这说明请求虽然通了,但返回结构不是你预期的。先打印完整返回体看看,可能是模型 ID 写错了,或者请求体格式不对。确认model字段填的是 TaoToken 支持的 Model ID,messages是数组且每个元素有role和content。如果返回的是错误信息对象,里面通常有error.message字段,按提示改。
第五类:OAuth 相关报错。如果你在 Cursor 里通过 OAuth 方式接入某些工具,报 OAuth 错误时,先检查回调地址是否配置正确,再检查 Token 是否过期。对于 TaoToken 的接入,通常用 API Key 方式即可,不需要走 OAuth。如果你用的工具强制要求 OAuth,确认它的配置里 Base URL 和 Key 是否填对。
第六类:Webhook 收到请求但 Cursor 侧显示投递失败。这通常是你的接收端返回了非 2xx 状态码,或者响应超时。确保签名校验通过后立即返回 200,把耗时处理放到异步。另外,生产环境必须用 HTTPS,HTTP 地址 Cursor 可能拒绝投递。
第七类:重试导致重复处理。Cursor 在收到错误状态码时会重试,如果你的处理逻辑不是幂等的,可能重复执行。用X-Webhook-ID做去重,把处理过的 ID 存起来,重复的直接返回 200 跳过。
排查的时候,建议把接收端的日志打全:请求头、原始 body、计算出的签名、收到的签名,对比一下就能快速定位。TaoToken 侧的报错,先看 HTTP 状态码,再看返回体的error字段,大部分问题都能从这两处找到线索。
6. 把回调链路接到你的开发流里
Webhook 接收端跑通、TaoToken API 验证通过之后,你可以把这条链路接到实际的开发流里。比如智能体产出 PR 后,接收端收到FINISHED事件,自动调用 TaoToken API 生成一段 PR 描述,再通过 GitHub API 更新到 PR 上;或者收到ERROR事件后,自动发一条通知到团队频道,附上summary和target.url。
长期跑编码和 Agent 任务的话,可以关注 TaoToken 的 Coding Plan,地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,适合需要稳定通道和统一 Key 管理的场景。如果你只是想先验证模型对话,用 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 就够了。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,遇到配置问题可以先查文档。
最后提醒一个实操细节:本地开发时,接收端跑在 localhost,Cursor 无法直接访问。你可以用内网穿透工具把本地端口暴露出去,但生产环境一定要用 HTTPS 域名。签名密钥不要写死在代码里,用环境变量管理。每次修改接收端逻辑后,先用 curl 模拟请求验证签名,再在 Cursor 里触发真实事件,这样排查成本最低。