웹훅 & Slack 통합
웹훅 & Slack 통합 (Webhooks & Slack Integration)
Langfuse에서 프롬프트 버전이 생성, 업데이트 또는 삭제될 때 실시간 알림을 받기 위해 웹훅을 사용할 수 있어요. 이를 통해 API를 폴링하지 않고도 CI/CD 파이프라인을 트리거하거나, 프롬프트 카탈로그를 동기화하거나, 변경 사항을 감사할 수 있어요.
출처: 문서
본문
왜 웹훅을 쓰나요?
- 프로덕션 모니터링: 프로덕션 프롬프트가 업데이트되면 알림 받기
- 팀 협업: 프롬프트 변경에 대해 모두에게 알리기
- 동기화: 프롬프트 카탈로그를 다른 시스템과 동기화
시작하기
Prompts로 이동해 Automations를 클릭하세요.
Create Automation을 클릭하세요.
감시할 이벤트를 선택하세요.
웹훅을 트리거할 프롬프트 버전 액션을 선택하세요:
- Created: 새 버전이 추가됨.
- Updated: 라벨이나 태그가 변경됨(두 개의 이벤트가 발생: 라벨/태그를 얻는 버전 하나와 잃는 버전 하나).
- Deleted: 버전이 제거됨.
(선택) 특정 프롬프트에서만 트리거되도록 필터링.
Webhook CallSlack Message
요청 구성
- URL: POST 요청을 받는 HTTPS 엔드포인트.
- Headers: 기본 헤더는 다음과 같아요:
Content-Type: application/jsonUser-Agent: Langfuse/1.0x-langfuse-signature: t=<timestamp>,v1=<signature>(아래 HMAC 서명 검증 참고)
- 필요하면 커스텀 정적 헤더 추가.
페이로드 검사
엔드포인트가 다음과 같은 JSON 본문을 받아요:
webhook-payload.json
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"timestamp": "2024-07-10T10:30:00Z",
"type": "prompt-version",
"apiVersion": "v1",
"action": "created",
"prompt": {
"id": "prompt_abc123",
"name": "movie-critic",
"version": 3,
"projectId": "xyz789",
"labels": ["production", "latest"],
"prompt": "As a {{criticLevel}} movie critic, rate {{movie}} out of 10.",
"type": "text",
"config": { "key": "value" },
"commitMessage": "Improved critic persona",
"tags": ["entertainment"],
"createdAt": "2024-07-10T10:30:00Z",
"updatedAt": "2024-07-10T10:30:00Z"
}
}
전달 확인
핸들러는 다음을 해야 해요:
- 수신을 확인하기 위해 HTTP 2xx 상태를 반환해야 함.
- 멱등적이어야 함 — Langfuse는 성공 응답을 받을 때까지 (지수 백오프로) 재시도할 수 있음.
진위 확인(권장)
각 요청은 x-langfuse-signature 헤더에 HMAC SHA-256 서명을 담고 있어요.
웹훅을 만들 때 시크릿(secret)을 얻으세요(나중에 재생성할 수 있어요).
Python SDKJS/TS SDK
import hmac
import hashlib
from typing import Optional
def verify_langfuse_signature(
raw_body: str,
signature_header: str,
secret: str,
) -> bool:
"""
Validate a Langfuse webhook/event signature.
Parameters
----------
raw_body : str
The request body exactly as received (no decoding or reformatting).
signature_header : str
The value of the `x-langfuse-signature` header, e.g. "t=1720701136,v1=0123abcd...".
secret : str
Your Langfuse signing secret.
Returns
-------
bool
True if the signature is valid, otherwise False.
"""
# Split "t=timestamp,v1=signature" into the two expected key/value chunks
try:
ts_pair, sig_pair = signature_header.split(",", 1)
except ValueError: # wrong format / missing comma
return False
# Extract values (everything after the first "=")
if "=" not in ts_pair or "=" not in sig_pair:
return False
timestamp = ts_pair.split("=", 1)[1]
received_sig_hex = sig_pair.split("=", 1)[1]
# Recreate the message and compute the expected HMAC-SHA256 hex digest
message = f"{timestamp}.{raw_body}".encode("utf-8")
expected_sig_hex = hmac.new(
secret.encode("utf-8"), message, hashlib.sha256
).hexdigest()
# Use constant-time comparison on the *decoded* byte strings
try:
return hmac.compare_digest(
bytes.fromhex(received_sig_hex), bytes.fromhex(expected_sig_hex)
)
except ValueError: # received_sig_hex isn't valid hex
return False
import crypto from "crypto";
export function verifyLangfuseSignature(
rawBody: string,
signatureHeader: string,
secret: string
): boolean {
const [tsPair, sigPair] = signatureHeader.split(",");
if (!tsPair || !sigPair) return false;
const timestamp = tsPair.split("=")[1];
const receivedSig = sigPair.split("=")[1];
const expectedSig = crypto
.createHmac("sha256", secret)
.update(`${timestamp}.${rawBody}`, "utf8")
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(receivedSig, "hex"),
Buffer.from(expectedSig, "hex")
);
}
Langfuse로 Slack 인증하기
- Langfuse는 OAuth를 통해 Slack에 연결해요.
- Slack에 대한 시크릿을 데이터베이스에 암호화해서 저장해요.
알림을 보낼 채널 선택
- 알림을 보내고 싶은 채널을 선택할 수 있어요.
- 드라이 런(dry run)을 실행해 메시지가 채널에 도착하는지 확인할 수 있어요.