Inference hooks 통합은 AI 보안 서버, 즉 Anthropic이 호출하는 HTTPS 서비스입니다. 관리 대상 요청마다 서버는 대화 트랜스크립트를 담은 서명된 POST 요청을 수신하고 허용 또는 거부 판정으로 응답합니다. 이 페이지에서는 해당 서버를 구축하기 위한 프로토콜, 즉 요청 및 판정 스키마, 서명 검증, 운영 계약을 문서화합니다.
Inference hooks를 활성화하고 엔드포인트를 지정하는 방법은 Inference hooks 구성을 참조하세요. Inference hooks가 무엇이며 언제 사용해야 하는지는 Inference hooks 개요를 참조하세요.
가장 작은 동작 통합은 각 요청을 읽고 허용하는 서버입니다. 다음 서버 중 하나를 실행하고 공개 https:// URL(예: TLS 종료 리버스 프록시 또는 터널 뒤)로 노출한 다음, 관리자가 이를 엔드포인트로 설정하고 연결을 테스트하도록 하세요. Test connection 결과는 서버가 반환한 허용 판정을 보고합니다.
# 실행 방법: python server.py
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
class VerdictHandler(BaseHTTPRequestHandler):
protocol_version = "HTTP/1.1" # keep the connection open between verdicts
def do_POST(self):
# 본문을 비웁니다. 트랜스크립트는 수 메가바이트에 달할 수 있습니다.
self.rfile.read(int(self.headers.get("Content-Length", 0)))
verdict = b'{"action": "allow"}'
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(verdict)))
self.end_headers()
self.wfile.write(verdict)
ThreadingHTTPServer(("", 8000), VerdictHandler).serve_forever()Anthropic은 관리자가 구성한 URL로 HTTPS POST 요청을 보냅니다. 구성된 전체 URL이 엔드포인트입니다. 고정된 경로 접미사가 없으므로 서버에 적합한 경로를 자유롭게 선택하세요.
Anthropic이 접근할 수 있는 곳에 AI 보안 서버를 호스팅하세요. 즉, 포트 443의 https:// URL이어야 하고, 공개적으로 라우팅 가능한 호스트여야 하며(사설, 루프백, 통신사급 NAT 범위는 연결 시점에 거부됨), 공개 CA 신뢰 저장소에 대해 유효성이 검증되는 인증서를 사용해야 하고, 리디렉션 없이 응답해야 합니다. 구성된 URL은 최종 목적지여야 합니다. 관리자가 URL을 설정하고 테스트하는 방법은 Inference hooks 구성에서 다룹니다.
모든 요청은 다음의 고정 헤더를 포함하며, 관리자가 구성한 사용자 지정 요청 헤더와, 조직에 서명 시크릿이 있는 경우 서명 검증하기에서 설명하는 webhook-* 서명 헤더도 함께 포함합니다.
| 헤더 | 값 |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
현재 훅 이벤트는 하나입니다. 바로 프롬프트 프레임으로, 관리 대상 추론 요청마다 추론이 시작되기 전에 한 번 전송됩니다. Anthropic은 AI 보안 서버가 응답하거나 판정 타임아웃이 경과할 때까지 요청을 보류합니다.
요청 본문은 다음 필드를 가진 JSON 객체입니다.
| 필드 | 타입 | 설명 |
|---|---|---|
type | string | 훅 이벤트입니다. 현재는 항상 "prompt"이며, 향후 다른 이벤트 타입이 도입될 예정이므로 인식되지 않는 값을 정상적으로 처리하세요(향후 호환성 참조). |
request_id | string | 상관관계를 위한 추론 호출별 불투명 식별자입니다. webhook-id 헤더와 동일합니다. |
tenant_id | string 또는 null | 요청이 속한 조직의 불투명 식별자입니다. |
actor | object | 요청이 귀속되는 주체로, type으로 구분됩니다(현재 전송되는 유일한 값은 "user"). id(태그가 지정된 식별자로, 동일한 계정에 대해 요청 간에 안정적임)와 email_address(사용 가능한 경우)를 포함합니다. id와 email_address는 모두 null일 수 있습니다. |
source | object | 요청을 발생시킨 애플리케이션: application(Source 값 참조). |
messages | array | 추론 시점까지의 대화 트랜스크립트입니다. 콘텐츠 블록을 참조하세요. |
session_id | string 또는 null | 대화가 존재하는 경우의 불투명 대화 식별자입니다. 파싱하지 마세요. Claude Code의 경우 이는 클라이언트가 주장하는 최선의 세션 식별자입니다. |
model | string 또는 null | 사용 가능한 경우 이 요청의 공개 모델 식별자입니다. |
metadata | object | 문자열 키와 문자열 값으로 구성된 예약된 확장 맵으로, 현재는 비어 있는 상태로 전송됩니다. 이 필드에서 아무것도 요구하지 말고, 부재, 존재, 그리고 나타나는 모든 키를 허용하세요. |
요청 본문 예시:
{
"type": "prompt",
"request_id": "req_abc123",
"tenant_id": "11111111-1111-1111-1111-111111111111",
"actor": {
"type": "user",
"id": "user_01AbCdEfGhIjKlMnOpQrStUv",
"email_address": "[email protected]"
},
"source": {
"application": "claude-ai"
},
"session_id": "22222222-2222-2222-2222-222222222222",
"model": "claude-sonnet-4-5",
"messages": [
{
"role": "user",
"content": [
{
"type": "text",
"text": "Summarize the attached report."
},
{
"type": "attachment",
"file_name": "q2-report.pdf",
"media_type": "application/pdf",
"size_bytes": 48213,
"text": "Q2 revenue grew 14% quarter over quarter..."
}
]
}
],
"metadata": {}
}messages의 각 항목은 user 또는 assistant의 role을 가지며(도구 결과는 공개 Messages API 콘텐츠 모델과 일치하도록 user 역할 아래에 나타남), type으로 구분되는 블록의 content 배열을 포함합니다.
블록 type | 필드 |
|---|---|
text | text: 텍스트 콘텐츠입니다. |
tool_use | id: 일치하는 도구 결과가 참조하는 식별자입니다. tool_name: 도구의 이름입니다. input: 모델이 도구에 전달한 인수입니다. |
tool_result | content: 도구의 출력을 텍스트로 나타낸 것으로, 각 부분은 줄바꿈으로 연결됩니다. 이미지와 같은 바이너리 부분은 플레이스홀더 마커로 대체되며, 원시 바이트는 절대 전송되지 않습니다. is_error: 도구 호출이 실패했는지 여부입니다. tool_name: 도구의 이름으로, 정책이 이전 블록을 상호 참조하지 않고도 도구 ID를 조건으로 사용할 수 있도록 합니다. tool_use_id: 일치하는 tool_use 블록의 id입니다. |
attachment | file_name: 원본 파일 이름 또는 경로입니다. media_type: 첨부 파일의 미디어 타입입니다. size_bytes: 원본 파일의 크기입니다. text: 사용 가능한 경우 첨부 파일의 텍스트 콘텐츠로, 추출된 문서 텍스트, 오디오 트랜스크립트 또는 링크 메타데이터 등이 해당됩니다. 원시 첨부 파일 바이트는 절대 전송되지 않습니다. |
인식하지 못하는 type을 가진 블록은 향후 호환성을 위한 추가 항목입니다. 보장되는 유일한 필드는 type입니다. 정책은 존재하는 다른 필드를 검사할 수 있지만, 인식되지 않는 타입 때문에 요청을 거부해서는 안 됩니다.
트랜스크립트는 추론 시점까지 최종 사용자가 보는 대화입니다. 즉, 트랜스크립트 텍스트, 도구 호출 및 그 결과, 추출된 첨부 파일 텍스트, 이전 턴이 포함됩니다. 시스템 프롬프트, 도구 정의, Anthropic 내부 컨텍스트, Claude의 숨겨진 추론 또는 원시 파일 바이트는 절대 포함되지 않습니다.
모든 블록이 제외된 턴은 완전히 생략되므로, 사용자와 어시스턴트가 엄격하게 번갈아 나타난다고 가정하지 마세요.
트랜스크립트는 잘리지 않고 전송되므로, 대용량 첨부 파일이 있는 긴 대화는 최대 10 MB까지의 큰 요청 본문을 생성합니다. 이 상한을 수용하도록 서버의 본문 제한을 높이세요. nginx의 client_max_body_size(1 MB)와 Express의 express.json()(100 kB)을 포함하여 여러 일반적인 기본값은 훨씬 작으며, 거부된 본문은 웹훅 실패로 간주되므로 Allow the request 실패 처리 설정에서는 크기가 초과된 프롬프트가 검사 없이 모델에 도달하게 됩니다.
source.application은 닫힌 열거형이 아니라 개방형 문자열입니다. 알려진 값은 claude-ai와 claude-code이며, 연결 테스트는 config-test를 사용합니다. 새로운 값이 나타날 수 있으며, 서버는 인식하지 못하는 값 때문에 요청을 거부해서는 안 됩니다.
source.application을 신뢰 경계가 아닌 참고용 라우팅 메타데이터로 취급하세요. 보안에 중요한 정책 결정을 이 값에만 의존하지 마세요.
두 결과 모두에 대해 HTTP 200과 JSON 판정 본문으로 응답하세요. action 필드가 구분자 역할을 합니다. 요청을 허용하려면:
{
"action": "allow"
}거부하려면:
{
"action": "deny",
"deny_reason": "This prompt appears to contain customer payment card data, which your organization's policy does not allow.",
"reference_id": "scan_01HXPT4R9V"
}| 필드 | 제약 조건 | 의미 |
|---|---|---|
action | "allow" 또는 "deny"; 필수 | allow는 추론을 진행시키고, deny는 거부합니다. |
deny_reason | string 또는 null; 최대 500자, 더 긴 값은 잘림 | action이 deny일 때 최종 사용자에게 표시되며, allow일 때는 무시됩니다. |
reference_id | string 또는 null; [A-Za-z0-9._:/-]에서 최대 50자 | 이 평가에 대한 자체 식별자입니다. 거부의 inference_hooks_request_denied 규정 준수 활동에 기록되며 최종 사용자에게는 절대 표시되지 않습니다. 불투명하게 유지하세요. 요청 콘텐츠나 개인 데이터를 포함하지 마세요. |
거부는 형식 문제로 인해 폐기되지 않습니다. 크기가 초과된 deny_reason은 잘리고, 형식이 잘못된 reference_id는 조용히 삭제되며, action은 여전히 적용됩니다.
그 반대는 성립하지 않습니다. 파싱 가능한 판정이 포함된 HTTP 200 이외의 모든 응답은 웹훅 실패이며, 판정 대신 조직의 실패 처리 설정이 적용됩니다. 특히:
allow 또는 deny 이외의 action 값은 웹훅 실패로 처리됩니다.Anthropic은 응답 본문을 최대 64 KiB까지 읽으며, 본문은 압축되지 않아야 합니다. 리디렉션은 따르지 않으며, 쿠키는 무시됩니다. 판정 본문의 알 수 없는 필드는 무시되므로, 여기에 문서화된 필드와 함께 더 풍부한 객체를 반환할 수 있습니다.
요청은 Standard Webhooks 사양에 따라 세 개의 헤더를 사용하여 서명됩니다. Anthropic은 헤더 이름을 소문자로 전송하며, 프록시가 대소문자를 변경할 수 있으므로 대소문자를 구분하지 않고 조회하세요.
| 헤더 | 내용 |
|---|---|
webhook-id | 이 전송에 대한 고유 식별자입니다. 본문의 request_id와 동일합니다. 멱등성 키 및 서명된 페이로드의 첫 번째 구성 요소로 사용하세요. |
webhook-timestamp | 요청이 서명된 시점의 Unix 시간(초)을 10진수 문자열로 나타낸 것입니다. 서버 시계에서 어느 방향으로든 5분 이상 차이 나는 타임스탬프는 거부하세요. |
webhook-signature | 공백으로 구분된 하나 이상의 v1,<base64> 값으로, 각각은 {webhook-id}.{webhook-timestamp}.{raw body bytes}에 대한 HMAC-SHA256입니다. 상수 시간 비교를 사용하여 값 중 하나라도 일치하면 요청을 수락하세요. |
대부분의 검증 버그는 다음 두 가지 세부 사항에서 발생합니다.
whsec_ 접두사 뒤의 값으로, 표준 base64 알파벳(+ 및 /)으로 인코딩되어 있으며, 헤더의 서명도 마찬가지입니다. URL-safe 디코더는 시크릿에 + 또는 /가 포함될 때마다 잘못된 키 바이트를 도출하는데, 이는 대부분의 경우에 해당합니다.조직에 서명 시크릿이 있으면 Anthropic이 보내는 모든 요청은 서명되며, Inference hooks를 활성화하려면 서명 시크릿이 필요하므로 서명되지 않은 상태로 도착하는 모든 요청을 거부하세요. 한 가지 예외가 있습니다. 조직의 첫 번째 저장 전에 전송된 연결 테스트는 서명 시크릿이 아직 존재하지 않기 때문에 서명되지 않은 상태로 도착합니다. 관리자가 시크릿이 존재함을 확인할 때까지 서명되지 않은 요청을 수락한 다음, 그 이후에는 거부하세요.
시크릿 교체는 즉시 전환되지만, 이전 시크릿으로 서명된 요청은 그 후 약 1분 동안, 그리고 이미 전송 중인 요청까지 계속 도착할 수 있습니다. 전환 중에 AI 보안 서버가 두 시크릿의 서명을 모두 수락하도록 하여 이러한 지연 요청이 거부되지 않도록 하세요.
다음 샘플은 서버 구현이므로 셸 탭이 없습니다. AI 보안 서버는 일회성 요청이 아니라 장기 실행 HTTPS 서비스이기 때문입니다. 각 샘플은 해당 언어의 표준 라이브러리만 사용합니다. Standard Webhooks 프로젝트는 대부분의 언어에 대한 검증 라이브러리도 게시하고 있습니다.
import base64
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 300
def verify(secret: str, headers: dict[str, str], body: bytes) -> bool:
"""Return True if the body was signed by Anthropic for this organization.
Anthropic sends header names in lowercase, but proxies are free to
re-case them, so normalize the lookup to lowercase.
"""
lowercased = {name.lower(): value for name, value in headers.items()}
try:
message_id = lowercased["webhook-id"]
timestamp = lowercased["webhook-timestamp"]
signatures = lowercased["webhook-signature"]
except KeyError:
return False # unsigned request: not from Anthropic
try:
signed_at = int(timestamp)
except ValueError:
return False
if abs(time.time() - signed_at) > TOLERANCE_SECONDS:
return False # replayed, or the clocks disagree
try:
key = base64.b64decode(secret.removeprefix("whsec_"), validate=True)
except ValueError:
return False # misconfigured secret: reject rather than crash
payload = f"{message_id}.{timestamp}.".encode() + body
expected = b"v1," + base64.b64encode(
hmac.new(key, payload, hashlib.sha256).digest()
)
# 바이트 비교: str에 대한 compare_digest는 비ASCII 입력에서 예외를 발생시킵니다.
return any(
hmac.compare_digest(expected, candidate.encode())
for candidate in signatures.split()
)관리자는 1~10,000ms 사이의 판정 타임아웃을 설정합니다(기본값 5,000ms). 이 예산은 연결, TLS 핸드셰이크, 요청, 응답을 포함한 전체 교환을 포괄합니다.
Anthropic은 연결 시도가 실패한 경우에만 100ms 지연 후 정확히 한 번 재시도합니다. 재시도는 동일한 타임아웃 예산을 공유하며 동일한 webhook-id와 동일한 서명을 포함합니다. AI 보안 서버가 응답한 후에는 교환이 절대 재시도되지 않습니다.
타임아웃, 200이 아닌 상태(리디렉션 포함), 파싱할 수 없거나 크기가 초과된 응답 본문, 도달할 수 없는 엔드포인트는 모두 웹훅 실패입니다. 웹훅 실패는 절대 거부가 되지 않습니다. 대신 조직의 실패 처리 설정이 영향을 받는 요청을 차단할지 검사 없이 진행할지 결정합니다.
AI 보안 서버에 기인하는 지속적인 웹훅 실패는 적용을 중단하는 서킷 브레이커를 작동시킵니다. Anthropic은 서버 연결을 중단하고, 모든 요청에 실패 처리가 적용됩니다. 복구는 관리자 측에서 이루어집니다. 서버를 수정한 다음 관리자가 Enforce verdicts를 다시 켜도록 하세요. 서킷 브레이커를 참조하세요.
적용은 조직의 모든 관리 대상 요청의 "latency"(지연 시간)에 AI 보안 서버의 왕복 시간을 추가합니다. 판정을 빠르게 유지하고, 대규모 조직에 배포하기 전에 서버를 부하 테스트하세요.
AI 보안 서버로의 요청은 Anthropic이 게시한 아웃바운드 IP 범위의 일부인 160.79.106.0/24에서 발생합니다. 같은 페이지의 인바운드 범위가 아니라 해당 블록을 허용 목록에 추가하세요. 인바운드 범위는 이를 포함하지 않습니다. 허용 목록 추가는 서버의 노출을 좁히지만 서명 검증을 대체하지는 않습니다. 해당 블록은 Inference hooks 외에도 Anthropic의 송신 트래픽을 전달합니다.
프로토콜은 올바르게 작성된 서버를 손상시키지 않고 확장됩니다. 서버는 다음을 무시해야 합니다.
metadata의 알 수 없는 키.source.application 값.actor.type 값. actor는 type으로 구분되는 유니온이며, 현재 전송되는 유일한 종류는 "user"입니다. 향후 종류는 type이 존재한다는 것만 보장합니다.type을 가진 콘텐츠 블록.인식되지 않는 블록 타입이나 필드 때문에 요청을 거부하지 마세요. 알고 있는 필드를 읽고 나머지는 건너뛰세요.
향후 다른 훅 이벤트 타입이 도입될 예정입니다. 새로운 이벤트 타입은 필드를 건너뛰는 것으로 서버가 처리할 수 없는 추가 항목입니다. 요청에는 여전히 판정이 필요합니다. 최상위 type이 인식하지 못하는 값인 경우 오류 상태가 아닌 허용 판정을 반환하세요. 오류 응답은 웹훅 실패이며, 지속적인 실패는 서킷 브레이커를 작동시킵니다.
프로덕션 AI 보안 서버는 와이어 프로토콜 외에도 몇 가지 설계 선택을 합니다.
webhook-id로 중복 제거하세요. webhook-id 헤더는 전송마다 고유하며 본문의 request_id와 동일하고, 연결 실패 재시도는 이를 재사용하므로 멱등성 키로 작동합니다. 판정을 기록하는 경우 이를 키로 사용하세요.
판정을 기록하고 거부를 조인하세요. 반환하는 각 판정을 reference_id와 함께 저장하세요. 모든 거부는 서버가 반환한 reference_id를 포함하는 inference_hooks_request_denied 규정 준수 활동으로 기록되므로, Activity Feed의 거부를 자체 시스템의 일치하는 레코드와 조인할 수 있습니다.
항상 허용하는 서버로 아카이빙하세요. 트랜스크립트를 단속하지 않고 실시간으로 캡처하려면 무조건 {"action": "allow"}를 반환하고 응답 후 프레임을 저장하세요. 이는 Compliance API를 폴링하는 것에 대한 푸시 기반 대안이며, 저장하기 전에 응답하면 왕복 시간이 사용자의 중요 경로에서 제외됩니다.
최종 사용자를 위해 deny_reason을 작성하세요. 반환하는 텍스트는 요청이 차단될 때 사용자가 보는 내용이며, 500자에서 잘립니다. 팀만 해석할 수 있는 스캐너 코드를 출력하는 대신, 어떤 종류의 콘텐츠를 제거해야 하는지 등 무엇을 변경해야 하는지 알려주세요.
Inference hooks를 활성화하고, 엔드포인트를 연결 및 테스트하며, 적용, 실패 처리, 배포를 제어합니다.
Inference hooks가 무엇인지, 판정 왕복이 어떻게 작동하는지, 언제 사용해야 하는지 알아봅니다.
Was this page helpful?