컨텍스트 허브 커밋 웹훅 구성하기
컨텍스트 허브 커밋 웹훅 구성하기
컨텍스트 허브 커밋 이벤트를 외부 HTTPS 엔드포인트로 보내고, LangSmith가 각 요청에 서명했는지 검증해요.
컨텍스트 허브 커밋 웹훅은 워크스페이스에서 에이전트 또는 스킬 커밋이 생성될 때마다 외부 서비스에 알려줘요. LangSmith Fleet을 통해 생성된 커밋을 포함해 컨텍스트 허브 변경으로부터 자동화를 트리거하는 데 사용하세요.
컨텍스트 허브 웹훅을 관리하려면 prompts:update 권한이 필요하며, 워크스페이스 관리자와 워크스페이스 편집자는 기본적으로 이 권한을 가져요.
출처: 문서
본문
웹훅 추가하기
각 웹훅은 워크스페이스 전체에 적용돼요. 구성된 모든 엔드포인트는 Fleet으로 생성된 커밋을 포함해 모든 에이전트 및 스킬 커밋을 수신해요. context_hub.commit.created.v1 이벤트는 저장소 또는 이벤트 유형별 필터링을 지원하지 않아요.
웹훅을 추가하려면:
- LangSmith UI에서 Settings > Integrations > Context Hub webhooks로 이동하세요.
- Add webhook을 클릭하세요.
- 공개적으로 접근 가능한 HTTPS URL을 입력하세요.
- (선택)
Authorization헤더 같은 커스텀 요청 헤더를 추가하세요. - Add webhook을 클릭하세요.
- 생성된 서명 시크릿을 복사해서 안전하게 보관하세요.
추가할 수 있는 구독 수는 워크스페이스 구성에 따라 달라져요.
웹훅 관리하기
웹훅 목록은 엔드포인트 URL과 커스텀 헤더 이름을 표시해요. 헤더 값과 서명 시크릿은 Reveal secrets를 클릭할 때까지 숨겨져 있어요. 컨텍스트 허브 웹훅을 관리할 권한이 있다면 나중에 공개할 수 있어요.
웹훅의 컨트롤을 사용해서 관리하세요:
- Edit webhook: HTTPS URL을 변경하거나 커스텀 헤더를 교체해요. 편집해도 서명 시크릿은 바뀌지 않아요.
- Roll signing secret: 새 서명 시크릿을 생성하고 공개해요. LangSmith는 이후 전달부터 새 시크릿을 즉시 사용하며, 이전 시크릿은 작동을 멈춰요. 웹훅을 검증하는 모든 소비자를 업데이트하세요.
- Delete webhook: 엔드포인트가 워크스페이스로부터 향후 컨텍스트 허브 커밋 이벤트를 받지 못하게 해요.
전달
LangSmith는 각 이벤트에 대해 JSON POST 요청을 보내요. 커스텀 헤더는 LangSmith가 커스텀 헤더를 적용한 후 설정하는 Content-Type 또는 X-LangSmith-Signature를 재정의할 수 없어요.
| 속성 | 값 |
|---|---|
| 메서드 | POST |
| URL | 공개적으로 접근 가능한 HTTPS 엔드포인트 |
| 콘텐츠 유형 | application/json |
| 서명 | 웹훅의 서명 시크릿으로 서명된 X-LangSmith-Signature 헤더 |
| 타임아웃 | 시도당 20초 |
| 시도 | 최대 4회: 최초 1회 + 재시도 최대 3회 |
| 재시도 조건 | 전송 실패, HTTP 408, 425, 429, 5xx 응답 |
| 영구 응답 | 기타 4xx 응답은 재시도되지 않음 |
| 응답 처리 | 400 미만의 상태는 성공. 응답 본문은 성공에 영향을 주지 않음. |
재시도에는 바이트 동일 요청 본문이 포함되며 이벤트 id를 유지해요. 다운스트림 효과를 만들기 전에 id로 이벤트를 중복 제거하세요.
서명 검증하기
각 요청에는 다음 형식의 X-LangSmith-Signature 헤더가 포함돼요:
sha256=<lowercase hex HMAC-SHA256 digest>
웹훅의 서명 시크릿으로 정확한 원시 요청 본문 바이트에 대해 HMAC-SHA256 다이제스트를 계산하세요. JSON을 파싱하기 전에 서명을 검증하고, 전체 헤더 값을 상수 시간으로 비교하세요. 검증 전에 본문을 파싱하고 재직렬화하면 바이트가 변경되어 서명이 무효화될 수 있어요.
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)
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)
);
}
이벤트 봉투(envelope)
외부 id, type, created, data 봉투는 고정되어 있어요. 이벤트 유형의 .v1 접미사는 data.commit 스키마를 버전 관리해요.
{
"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": "[email protected]",
"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 | 이벤트가 대기열에 들어간 UTC Unix 초. |
data |
object | 버전 관리된 이벤트 데이터. data.commit을 포함. |
data.commit
data.commit 객체는 이벤트를 트리거한 컨텍스트 허브 커밋을 설명해요.
| 필드 | 유형 | 설명 |
|---|---|---|
repo_id |
UUID | 컨텍스트 허브 저장소 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 UI의 커밋 딥 링크. |
files_changed |
array | 커밋에 포함된 파일 변경. 각 항목은 path와 action을 포함. |
data.commit.files_changed
각 항목은 변경된 경로를 요약해요. 파일 콘텐츠는 포함하지 않아요.
| 필드 | 유형 | 설명 |
|---|---|---|
path |
string | 커밋으로 변경된 경로. |
action |
string | 변경 유형: added, modified 또는 removed. |
이벤트 버전 처리하기
data.commit을 파싱하기 전에 전체 이벤트 유형으로 분기하세요:
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으로 파싱하려고 시도하는 대신 무시하고, 호환 가능한 추가가 핸들러를 깨지 않도록 알 수 없는 필드를 허용하세요.
다음 단계
- 컨텍스트 허브 사용하기: 에이전트 및 스킬 커밋을 만들고, 검사하고, 승격해 보세요.