主题
配置 Context Hub 提交 webhook
将 Context Hub 的提交事件发送到外部 HTTPS 端点,并验证每个请求都由 LangSmith 签名。
Context Hub 的提交 webhook 会在你的工作区中创建智能体或技能提交时通知外部服务。你可以用它们在 Context Hub 发生变更时触发自动化,包括通过 LangSmith Fleet 创建的提交。
管理 Context Hub webhook 需要 prompts:update 权限,工作区管理员和工作区编辑者默认拥有该权限。
添加 webhook
每个 webhook 都作用于整个工作区。所有已配置的端点都会收到每一个智能体和技能提交,包括由 Fleet 创建的提交。context_hub.commit.created.v1 事件不支持按仓库或事件类型进行过滤。
要添加 webhook:
- 在 LangSmith UI 中,进入 Settings > Integrations > Context Hub webhooks。
- 点击 Add webhook。
- 输入一个可公开访问的 HTTPS URL。
- (可选)添加自定义请求头,例如
Authorization头。 - 点击 Add webhook。
- 复制生成的签名密钥,并妥善保存。
你可以添加的订阅数量取决于你的工作区配置。
管理 webhook
webhook 列表会显示端点的 URL 和自定义请求头的名称。请求头的值和签名密钥都会保持隐藏,直到你点击 Reveal secrets。如果你之后仍拥有管理 Context Hub webhook 的权限,可以随时再次查看它们。
使用 webhook 上的控件来管理它:
- Edit webhook:修改 HTTPS URL 或替换它的自定义请求头。编辑不会更改签名密钥。
- Roll signing secret:生成并展示一个新的签名密钥。LangSmith 会立即使用新密钥进行后续投递,旧密钥随即失效。请更新所有校验该 webhook 的调用方。
- Delete webhook:让该端点不再接收来自此工作区的后续 Context Hub 提交事件。
投递
LangSmith 会为每个事件发送一个 JSON POST 请求。自定义请求头无法覆盖 Content-Type 或 X-LangSmith-Signature,这两个请求头由 LangSmith 在应用自定义请求头之后设置。
| 属性 | 值 |
|---|---|
| Method | POST |
| URL | 可公开访问的 HTTPS 端点 |
| Content type | application/json |
| Signature | X-LangSmith-Signature 请求头,使用 webhook 的签名密钥进行签名 |
| Timeout | 每次尝试 20 秒 |
| Attempts | 最多 4 次尝试:1 次初始尝试和最多 3 次重试 |
| Retry conditions | 传输失败,以及 HTTP 408、425、429 和 5xx 响应 |
| Permanent responses | 其他 4xx 响应不会重试 |
| Response handling | 状态码低于 400 即视为成功。响应体不影响成功与否。 |
重试时使用完全相同请求体字节内容,并保留事件的 id。在产生下游影响之前,请先按 id 对事件去重。
校验签名
每个请求都包含一个如下格式的 X-LangSmith-Signature 请求头:
text
sha256=<lowercase hex HMAC-SHA256 digest>使用 webhook 的签名密钥,对原始请求体的精确字节内容计算 HMAC-SHA256 摘要。请在解析 JSON 之前校验签名,并以恒定时间比较完整的请求头值。如果在校验之前先解析并重新序列化请求体,可能会改变它的字节内容,从而导致签名失效。
python
import hashlib
import hmac
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)typescript
import { createHmac, timingSafeEqual } from "node:crypto";
export function verifyLangSmithSignature({
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)
);
}事件信封
外层的 id、type、created 和 data 信封结构已经冻结。事件类型上的 .v1 后缀用于对 data.commit 的 schema 进行版本管理。
json
{
"id": "0198...",
"type": "context_hub.commit.created.v1",
"created": 1720000000,
"data": {
"commit": {
"repo_id": "...",
"repo_handle": "my-agent",
"repo_type": "agent",
"commit_hash": "newcommithash0002",
"parent_commit_hash": "parentcommithash0001",
"created_at": "2023-11-14T22:13:20Z",
"created_by": "user@example.com",
"url": "https://smith.example.com/context/my-agent/newcommithash0002",
"files_changed": [
{ "path": "skills/kept", "action": "modified" },
{ "path": "skills/added", "action": "added" },
{ "path": "skills/gone", "action": "removed" }
]
}
}
}| 字段 | 类型 | 描述 |
|---|---|---|
id | UUID | 唯一的事件标识符,在多次重试中保持稳定。可用它对事件去重。 |
type | string | 精确的事件类型。目前为 context_hub.commit.created.v1。 |
created | integer | 事件入队时的 Unix 秒数(UTC)。 |
data | object | 带版本管理的事件数据。包含 data.commit。 |
data.commit
data.commit 对象描述了触发该事件的 Context Hub 提交。
| 字段 | 类型 | 描述 |
|---|---|---|
repo_id | UUID | Context Hub 仓库 ID。 |
repo_handle | string | 仓库句柄。 |
repo_type | string | 仓库类型:agent 或 skill。 |
commit_hash | string | 新提交的哈希值。 |
parent_commit_hash | string | 父提交的哈希值。对于初始提交或不可用时省略。 |
created_at | string | 提交创建时间的 RFC 3339 时间戳。 |
created_by | string | 创建该提交的 LangSmith 用户 ID。不可用时省略。 |
url | string | 指向该提交在 LangSmith 界面中位置的深链。 |
files_changed | array | 提交中包含的文件变更。每个条目包含 path 和 action。 |
data.commit.files_changed
每个条目概述一处发生变更的路径,不包含文件内容。
| 字段 | 类型 | 描述 |
|---|---|---|
path | string | 提交所变更的路径。 |
action | string | 变更类型:added、modified 或 removed。 |
处理事件版本
在解析 data.commit 之前,请先按完整的事件类型进行分支:
typescript
if (event.type === "context_hub.commit.created.v1") {
await handleCommitCreatedV1(event.data.commit);
} else {
// Ignore unknown event types and versions safely.
}对 data.commit 的破坏性变更会使用新的事件类型后缀,例如 .v2。请忽略未知类型,而不要尝试将其按 v1 解析;同时允许未知字段,这样兼容性的新增内容就不会破坏你的处理程序。
下一步
- 使用 Context Hub:创建、检查并提升 agent 和 skill 提交。