워크스페이스 단위 웹훅은 워크스페이스 전체에 적용되는 기본 웹훅입니다.
에이전트에 개별 웹훅이 없으면 워크스페이스 웹훅으로 자동 fallback됩니다.
이 페이지는 웹훅 설정과 HMAC 서명 검증을 다룹니다.
이벤트 타입과 페이로드 예시는 에이전트 단위 웹훅 문서를 참고하세요.
웹훅 URL 설정
웹훅 URL은 두 곳에서 설정할 수 있습니다.
| 레벨 | 설정 위치 | 용도 |
|---|
| 워크스페이스 | 설정 > 웹훅 | 워크스페이스 기본 웹훅 URL |
| 에이전트 | 에이전트 대시보드 > 웹훅 설정 탭 | 에이전트별 개별 웹훅 URL |
에이전트 웹훅 URL이 설정된 경우 해당 URL이 우선 적용됩니다.
미설정 시 워크스페이스 웹훅 URL로 자동 fallback됩니다.
Transfer Agent로 전환되더라도 최초 인입 Agent의 웹훅 URL이 사용됩니다.
웹훅 URL 등록
대시보드 설정 > 웹훅에서 워크스페이스 웹훅 URL을 입력하세요.
빈 값으로 저장하면 웹훅 전송이 중지됩니다.
서명 키 생성
같은 화면에서 웹훅 서명 키를 생성하세요.
생성된 키는 한 번만 표시되므로 안전한 곳에 보관하세요.
vox.ai는 IP 화이트리스트와 HMAC-SHA256 서명 두 가지 보안 레이어를 제공합니다.
IP 화이트리스트
vox.ai 웹훅 요청은 고정 IP 34.146.189.242에서 전송됩니다.
방화벽 또는 리버스 프록시에서 이 IP만 허용하세요.
HMAC-SHA256 서명
웹훅 요청에는 아래 헤더가 포함됩니다.
| 헤더 | 설명 | 예시 |
|---|
X-Webhook-Timestamp | 서명 생성 시점 (Unix, 초) | 1738900000 |
X-Webhook-Signature | HMAC-SHA256 서명 | sha256=a1b2c3... |
서명은 다음 규칙으로 계산하세요.
signed_payload = "{timestamp}.{json_body}"
signature = HMAC_SHA256(secret, signed_payload)
json_body는 키 정렬(sort_keys) + 공백 제거로 직렬화한 문자열을 사용하세요.
normalized_json은 키 정렬 + 공백 없는 JSON입니다.
Python: json.dumps(payload, sort_keys=True, separators=(",", ":"))
JavaScript: JSON.stringify(sortKeys(payload)) (재귀적 키 정렬 필요)
웹훅 서명 키 관리
대시보드 설정 > 웹훅에서 서명 키를 관리하세요.
키 생성
설정 > 웹훅 페이지에서 키 발급 버튼을 클릭하세요.
키 복사 및 저장
생성된 키를 복사하여 서버 환경 변수에 저장하세요.# .env
VOX_WEBHOOK_KEY=a3f7c9d2e5b8a1f4c6d9e2b5a8f1c4d7e0b3a6f9c2d5e8b1a4f7c0d3e6b9a2f5
웹훅 서명 키는 생성 직후 한 번만 전체 값이 표시됩니다.
이후에는 마지막 4자리만 확인할 수 있으므로, 반드시 안전한 곳에 저장하세요.
서명 키 재발급 — 기존 키 삭제 후 새 키를 생성합니다. 기존 키로 서명된 웹훅은 즉시 검증 실패합니다.
서명 키 삭제 — HMAC 서명이 비활성화되고, 웹훅 요청에 서명 헤더가 포함되지 않습니다.
서버 구현
HMAC 서명 검증이 포함된 엔드포인트 예제를 참고하세요.
import hmac, hashlib, json, time, os
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
WEBHOOK_KEY = os.environ["VOX_WEBHOOK_KEY"]
TOLERANCE = 5 * 60 # 5분
def verify_webhook(body: bytes, timestamp: str, signature: str, secret: str) -> bool:
if abs(time.time() - int(timestamp)) > TOLERANCE:
return False
normalized = json.dumps(json.loads(body), sort_keys=True, separators=(",", ":"))
expected = hmac.new(
secret.encode(), f"{timestamp}.{normalized}".encode(), hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature.removeprefix("sha256="))
@app.post("/webhook")
async def handle_webhook(request: Request):
body = await request.body()
ts = request.headers.get("X-Webhook-Timestamp", "")
sig = request.headers.get("X-Webhook-Signature", "")
if not verify_webhook(body, ts, sig, WEBHOOK_KEY):
raise HTTPException(status_code=401)
data = json.loads(body)
# 페이로드 처리 ...
서명 검증 후 HTTP 200을 빠르게 반환하세요.
후속 처리(저장/분석 등)는 비동기로 분리하세요.
보안 권장사항
두 레이어 모두 사용
IP 화이트리스트와 HMAC 서명을 함께 사용하세요. 네트워크와 애플리케이션 레벨 모두 보안을 확보할 수 있습니다.
Timestamp 검증
타임스탬프가 현재 시간과 5분 이내인지 확인하여 Replay 공격을 방지하세요.
Timing-safe 비교
hmac.compare_digest (Python) 또는 crypto.timingSafeEqual (Node.js)로 Timing 공격을 방지하세요.
키 안전 보관
웹훅 서명 키는 환경 변수 또는 시크릿 매니저에 저장하고, 코드에 하드코딩하지 마세요.
관련 문서
워크스페이스 웹훅, organization webhook, HMAC, 서명 검증, 웹훅 보안, IP 화이트리스트, 서명 키