将 LangSmith 检测到的代理问题转发到您的事件管理、寻呼或聊天工具中。 LangSmith Engine 在打开新问题或将新追踪链接到已打开的问题时,向您的端点发送 Webhook 事件。
要配置 Webhook 订阅,请打开 **Engine 设置** 面板,在 **Engine** 标签页。参见 配置 LangSmith Engine.
传递
LangSmith 发送一个 POST 请求,带有 JSON 正文,发送到您的 Webhook URL。该请求使用 Content-Type: application/json 并包含您附加到订阅的任何自定义标头。
| 属性 | 值 |
|---|---|
| 方法 | POST |
| 正文 | JSON, 通用信封 见下文 |
| 方案 | http:// 和 https:// 均被接受。 https:// 强烈推荐 |
| 签名 | X-LangSmith-Signature 标头,使用订阅的签名密钥签名 |
| 超时 | 每次尝试 20 秒 |
| 尝试次数 | 最多 4 次尝试(1 次初始加 3 次指数退避重试),针对传输错误、HTTP 408, 425, 429和任何 HTTP 5xx。其他 4xx 响应被视为永久性的,不会重试 |
| 响应 | 成功仅由状态码决定。响应正文被忽略。 |
自定义请求头
您可以为每个订阅附加任意请求头(例如, Authorization: Bearer …)用于在您的端点验证调用者身份。 Content-Type 由 LangSmith 设置,无法被覆盖。
签名密钥
每个订阅都有一个签名密钥。LangSmith 使用此密钥对原始 webhook 请求体进行签名,并将结果放在 X-LangSmith-Signature header.
请求头值的格式如下:
sha256=<hex-encoded HMAC-SHA256 digest>
在解析或处理负载之前验证签名。HMAC 输入是精确的原始请求体字节,HMAC 密钥是订阅的签名密钥。请勿在验证前解析并重新序列化 JSON 体。
from typing import Optional
def verify_langsmith_signature(
*,
body: bytes,
signing_secret: str,
signature_header: Optional[str],
) -> bool:
if not signature_header or not signature_header.startswith("sha256="):
return False
expected = "sha256=" + hmac.new(
signing_secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature_header)
body,
signingSecret,
signatureHeader,
}: {
body: Buffer;
signingSecret: string;
signatureHeader: string | undefined;
}) {
if (!signatureHeader?.startsWith("sha256=")) {
return false;
}
const expected = `sha256=${createHmac("sha256", signingSecret)
.update(body)
.digest("hex")}`;
const expectedBytes = Buffer.from(expected);
const actualBytes = Buffer.from(signatureHeader);
return (
expectedBytes.length === actualBytes.length &&
timingSafeEqual(expectedBytes, actualBytes)
);
}
轮换签名密钥
当签名密钥可能已泄露时,或当您组织的凭证轮换策略需要新密钥时,请轮换签名密钥。
要轮换密钥,请在 **引擎设置**中打开订阅行,然后点击 **轮换签名密钥**,然后确认。LangSmith 会生成新的签名密钥并立即用于后续 webhook 投递。前一个密钥在轮换完成后立即停止为投递进行签名。
轮换密钥后,更新每个验证 X-LangSmith-Signature 的消费者以使用新值。
严重程度过滤
每个订阅都有一个 severity_threshold 阈值。对于问题事件,只有当问题的 0 to 3小于或等于阈值时才会投递事件。数字越小越紧急。 severity | 严重程度 | 含义 |
| 紧急 |
|---|
0 |
1 |
2 |
3 例如,具有 |
的订阅仅接收 severity_threshold: 1 (0)和 URGENT (1)问题的相关事件。 HIGH 严重程度阈值不适用于
,因为运行失败事件的作用域是引擎会话而非特定问题。 issue.agent_run.failed事件类型过滤
每个订阅都指定要接收的
事件类型 。未指定明确列表的订阅默认接收 事件包装 ["issue.created"].
投递到您端点的每个事件都使用相同的外层 JSON 结构。
| 字段 | 类型 | 描述 |
| UUID | 本次投递的唯一标识符。在重试时保持稳定,用于去重。 |
|---|---|
id | string |
type | integer |
created | UUID |
request_id 批量合并 [ | object |
data 。仅在 data.object时包含 data.trace 事件上。 issue.trace.added events. |
问题 data.object
对于 issue.created 和 issue.trace.added, data.object 是问题的快照。请将其视为事件生成时问题的权威状态。
| Field | Type | Description |
|---|---|---|
id | UUID | 问题 ID。 |
name | string | 问题的简短标题。 |
description | string | 人类可读的描述。 |
severity | integer | 0 (紧急) 到 3 (低)。参见 严重程度过滤. |
tenant_id | UUID | 问题所属的工作空间。 |
tenant_name | string | 工作空间显示名称。 |
session_id | UUID | 问题所属的追踪项目。 |
session_name | string | 追踪项目名称。 |
url | string | LangSmith UI 中问题的深度链接。 |
运行失败 data.object
对于 issue.agent_run.failed, data.object 描述失败的 Engine 运行。
| Field | Type | Description |
|---|---|---|
tenant_id | UUID | 运行所属的工作空间。 |
tenant_name | string | 工作空间显示名称。 |
session_id | UUID | 运行所属的追踪项目。 |
session_name | string | 追踪项目名称。 |
url | string | UI 中 LangSmith 项目的深度链接。 |
thread_id | string | Engine 线程 ID。 |
run_id | string | Engine 运行 ID。不可用时省略。 |
status | string | 最终运行状态。 |
error_message | string | 失败运行的错误文本。不可用时省略。 |
occurred_at | string | 失败发生时间的 RFC 3339 时间戳。 |
data.trace
data.trace 仅在 issue.trace.added events.
| Field | Type | Description |
|---|---|---|
run_id | UUID | 链接到问题的运行 ID。 |
trace_id | UUID | 包含该运行的追踪 ID。 |
start_time | string | 运行开始时间的 RFC 3339 时间戳。 |
comment | string \ | null |
批量合并
单个上游操作可以产生多个 webhook 事件。当 Engine 打开一个新问题并附加五个追踪到它时,您会收到一个 issue.created 事件和五个 issue.trace.added 事件,它们都共享相同的 request_id。使用 request_id 将这些分组为单个下游通知。
事件类型
以下事件类型是 LangSmith Engine 今天发送的完整集合。未来可能会添加新类型,因此处理程序应该忽略未知的 type 值而不是失败。
issue.created
当 LangSmith Engine 创建新问题时发送。 data.trace 被省略。
{
"id": "b91c1f0e-7c4a-4f53-9d3e-9f1c8e7a2b10",
"type": "issue.created",
"created": 1747238400,
"request_id": "0d2f4f6a-2a3a-4b6e-9b87-5d5b6e8c9a01",
"data": {
"object": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"name": "Tool selection inconsistency",
"description": "Agent repeatedly calls the search tool with identical arguments before terminating.",
"severity": 1,
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_name": "Acme Workspace",
"session_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"session_name": "prod-api",
"url": "https://smith.langchain.com/o/11111111-2222-3333-4444-555555555555/projects/p/66666666-7777-8888-9999-aaaaaaaaaaaa?tab=5&issue=9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
}
}
}
issue.trace.added
当新追踪链接到现有问题时发送。 data.trace 描述链接的追踪。
{
"id": "c02e3a4b-5c6d-7e8f-9a0b-1c2d3e4f5a6b",
"type": "issue.trace.added",
"created": 1747238410,
"request_id": "0d2f4f6a-2a3a-4b6e-9b87-5d5b6e8c9a01",
"data": {
"object": {
"id": "9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d",
"name": "Tool selection inconsistency",
"description": "Agent repeatedly calls the search tool with identical arguments before terminating.",
"severity": 1,
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_name": "Acme Workspace",
"session_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"session_name": "prod-api",
"url": "https://smith.langchain.com/o/11111111-2222-3333-4444-555555555555/projects/p/66666666-7777-8888-9999-aaaaaaaaaaaa?tab=5&issue=9a8b7c6d-5e4f-3a2b-1c0d-9e8f7a6b5c4d"
},
"trace": {
"run_id": "f1e2d3c4-b5a6-9788-6655-44332211ffee",
"trace_id": "abcdefab-1234-5678-9abc-def012345678",
"start_time": "2026-05-14T12:30:00Z",
"comment": "Reproduces the same tool-loop pattern."
}
}
}
issue.agent_run.failed
当 LangSmith Engine 运行失败时发送。此事件是会话范围的,因此不包含 data.trace 并且不使用严重性过滤。
{
"id": "4d0e8db2-81e6-4491-b8e5-b13a8f5afc0d",
"type": "issue.agent_run.failed",
"created": 1747238500,
"request_id": "f6bbd48a-0386-403d-9344-31051264b45f",
"data": {
"object": {
"tenant_id": "11111111-2222-3333-4444-555555555555",
"tenant_name": "Acme Workspace",
"session_id": "66666666-7777-8888-9999-aaaaaaaaaaaa",
"session_name": "prod-api",
"url": "https://smith.langchain.com/o/11111111-2222-3333-4444-555555555555/projects/p/66666666-7777-8888-9999-aaaaaaaaaaaa",
"thread_id": "thread-123",
"run_id": "run-456",
"status": "error",
"error_message": "RuntimeError: missing API key",
"occurred_at": "2026-05-14T12:45:00Z"
}
}
}
测试您的端点
在将真实订阅指向您的端点之前,发送示例负载以验证它在 20 秒超时内接受并确认:
curl -X POST https://your-endpoint.example.com/webhook \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $WEBHOOK_SECRET" \
-d @sample-issue-created.json
使用来自的示例正文 issue.created as sample-issue-created.json。验证以下内容:
- - 自定义
Authorization标头到达并与您在订阅上配置的密钥匹配。 - - 处理程序通过其键持久保存事件
id以便重试被去重。 - - 处理程序返回
2xx在启动缓慢的下游工作之前。
安全
- - Webhook URL 在订阅创建时和交付时都会被验证。私有和元数据 IP 范围在 SaaS 中被阻止。两者都
http://和https://都被接受;使用https://以便负载和任何自定义标头不会以明文形式发送。 - - LangSmith 使用订阅的签名密钥对 webhook 正文进行签名。在处理负载之前验证
X-LangSmith-Signature。 - - 您还可以在订阅上设置自定义标头,例如
Authorization: Bearer …,用于在您的端点进行路由或额外身份验证。 - - 根据事件进行去重
id以便重试交付不会导致重复通知。
最佳实践
- 快速确认。 在您已持久保存事件后立即响应
2xx。将缓慢工作(扇出、分页、下游 API 调用)移至队列,以便您的处理程序保持在 20 秒超时范围内。 - 容忍未知的事件类型。 忽略
type您的处理程序无法识别的值。新事件类型可能会在未事先通知的情况下添加。 - 容忍新字段。 使用宽松的模式解析负载。新字段可能会在未事先通知的情况下添加到现有事件类型中。