開發 Inference hooks 整合
建置 AI 安全伺服器,用以接收已簽章的 Inference hooks 請求、驗證這些請求,並回傳允許或拒絕的裁決。
Inference hooks 整合是一個 AI 安全伺服器:一個由 Anthropic 呼叫的 HTTPS 服務。對於每個受管控的請求,您的伺服器會收到一個帶有對話逐字稿的已簽章 POST,並以允許或拒絕的「verdict」(裁決)回應。本頁說明建置該伺服器的協定:請求與裁決的結構描述、簽章驗證,以及運作契約。
若要啟用 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):
# 讀取並清空請求主體;逐字稿可能達數 MB。
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 就是端點:沒有固定的路徑後綴,因此您可以選擇任何適合您伺服器的路徑。
請將您的 AI 安全伺服器託管在 Anthropic 可以連線到的位置:位於連接埠 443 的 https:// URL、位於可公開路由的主機上(私有、loopback 與電信級 NAT 範圍會在連線時被拒絕)、具備可通過公開 CA 信任儲存區驗證的憑證,並且回應時不進行重新導向。設定的 URL 必須是最終目的地。不支援反向通道主機(ngrok 及類似的通道服務):Anthropic 的網路政策會封鎖它們。請將您的伺服器託管在您所控制的網域上。設定 Inference hooks 說明了您的管理員如何設定與測試該 URL。
每個請求都帶有下列固定標頭,以及您的管理員所設定的任何自訂請求標頭;一旦您的組織擁有簽章密鑰,還會帶有驗證簽章中所述的 webhook-* 簽章標頭:
| 標頭 | 值 |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
目前只有一種 hook 事件:prompt frame(提示框架),每個受管控的推論請求在推論開始前傳送一次。Anthropic 會暫停該請求,直到您的 AI 安全伺服器回應或裁決逾時為止。
提示框架
請求主體是一個具有下列欄位的 JSON 物件:
| 欄位 | 類型 | 說明 |
|---|---|---|
type | string | hook 事件。目前一律為 "prompt";未來會引入其他事件類型,因此請妥善處理無法辨識的值(請參閱向前相容性)。 |
request_id | string | 用於關聯的不透明、每次推論呼叫專屬的識別碼。等於 webhook-id 標頭。 |
tenant_id | string 或 null | 該請求所屬組織的不透明識別碼。 |
actor | object | 該請求所歸屬的主體,以 type 區分(目前僅傳送 "user"):id(帶標記的識別碼,對同一帳戶的各請求保持穩定)與 email_address(若可取得)。id 與 email_address 皆可能為 null。 |
source | object | 來源應用程式:application(請參閱來源值)。 |
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": "alice@example.com"
},
"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 中的每個項目都有一個 role,值為 user 或 assistant(工具結果出現在 user 角色之下,與公開 Messages API 的內容模型一致),以及一個以 type 區分的區塊 content 陣列:
區塊 type | 欄位 |
|---|---|
text | text:文字內容。 |
tool_use | id:對應的工具結果所參照的識別碼。tool_name:工具的名稱。input:模型傳遞給工具的引數。 |
tool_result | content:工具的輸出,以文字表示,各部分以換行符號連接;影像等二進位部分會以佔位標記取代,且絕不會傳送原始位元組。is_error:工具呼叫是否失敗。tool_name:工具的名稱,讓政策可以依工具身分設定條件,而無需交叉參照先前的區塊。tool_use_id:對應的 tool_use 區塊的 id。 |
attachment | file_name:原始檔案名稱或路徑。media_type:附件的媒體類型。size_bytes:原始檔案的大小。text:附件的文字內容(若可取得),例如擷取的文件文字、音訊逐字稿或連結中繼資料。絕不會傳送原始附件位元組。 |
您無法辨識其 type 的區塊是向前相容的新增項目。它唯一保證的欄位是 type;您的政策可以檢查存在的任何其他欄位,但不得因為無法辨識的類型而拒絕請求。
逐字稿包含的內容
逐字稿是終端使用者所看到的對話,截至推論時間點:逐字稿文字、工具呼叫及其結果、擷取的附件文字,以及先前的回合。它絕不包含系統提示、工具定義、Anthropic 內部上下文、Claude 的隱藏推理,或原始檔案位元組。
若某個回合的每個區塊都被排除,則該回合會被完全省略,因此請勿假設 user 與 assistant 嚴格交替。
逐字稿會以未截斷的形式傳送,因此帶有大型附件的長對話會產生大型請求主體,上限為 10 MB。請提高您伺服器的主體大小限制以接受該上限。數個常見的預設值遠小於此,包括 nginx 的 client_max_body_size 為 1 MB,以及 Express 的 express.json() 為 100 kB,而被拒絕的主體會被視為 webhook 失敗,因此在 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;最多 50 個字元,取自 [A-Za-z0-9._:/-] | 您自己對此次評估的識別碼。它會記錄在該拒絕的 inference_hooks_request_denied 合規活動上,且絕不會顯示給終端使用者。請保持其不透明:不含請求內容,也不含個人資料。 |
拒絕絕不會因為格式問題而被捨棄:過長的 deny_reason 會被截斷,格式錯誤的 reference_id 會被靜默丟棄,而 action 仍會被遵循。
反之則不成立。除了帶有可解析裁決的 HTTP 200 之外,任何其他回應都是 webhook 失敗,此時會套用您組織的失敗處理設定,而非裁決。特別是:
- 請勿以錯誤狀態碼表示拒絕。非 200 的回應是失敗,而非拒絕。
- 除
allow或deny以外的任何action值都會被視為 webhook 失敗。
Anthropic 最多讀取 64 KiB 的回應主體,且主體必須未經壓縮。不會跟隨重新導向,且會忽略 cookie。裁決主體中的未知欄位會被忽略,因此您可以在本頁所記載的欄位之外回傳更豐富的物件。
驗證簽章
請求依據 Standard Webhooks 規範進行簽章,使用三個標頭。Anthropic 以小寫傳送標頭名稱,而代理伺服器可以自由變更其大小寫,因此請以不區分大小寫的方式查找它們。
| 標頭 | 內容 |
|---|---|
webhook-id | 此次傳遞的唯一識別碼。等於主體的 request_id。請將其用作冪等鍵,以及已簽章承載的第一個組成部分。 |
webhook-timestamp | 請求簽章時的 Unix 時間(以秒為單位),以十進位字串表示。請拒絕與您伺服器時鐘相差超過五分鐘(任一方向)的時間戳記。 |
webhook-signature | 一個或多個以空格分隔的 v1,<base64> 值,每個都是對 {webhook-id}.{webhook-timestamp}.{raw body bytes} 計算的 HMAC-SHA256。若任一值與您計算的值相符,則接受該請求,並使用常數時間比較。 |
大多數驗證錯誤源自兩個細節:
- 驗證原始位元組。 請對收到的主體原樣計算 HMAC,在任何 JSON 解析或重新編碼之前進行。
- 使用標準 base64 解碼器解碼密鑰。 簽章密鑰是
whsec_前綴之後的值,以標準 base64 字母表(+與/)編碼,標頭中的簽章亦然。只要密鑰包含+或/(大多數情況皆如此),URL 安全的解碼器就會推導出錯誤的金鑰位元組。
一旦您的組織擁有簽章密鑰,Anthropic 傳送的每個請求都會經過簽章,而且啟用 Inference hooks 需要簽章密鑰,因此請拒絕任何未簽章送達的請求。有一個例外:在您的組織首次儲存之前傳送的連線測試會以未簽章的形式送達,因為簽章密鑰尚不存在。在您的管理員確認密鑰存在之前,請接受未簽章的請求,之後再拒絕它們。
輪替密鑰是立即切換,但以先前密鑰簽章的請求在之後約一分鐘內仍可能送達,再加上任何已在傳輸中的請求。請讓您的 AI 安全伺服器在切換期間接受來自兩個密鑰的簽章,以免這些延遲送達的請求被拒絕。
下列範例是伺服器實作,因此沒有 shell 分頁: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()
)
# 比較位元組:compare_digest 對 str 遇到非 ASCII 輸入時會拋出例外。
return any(
hmac.compare_digest(expected, candidate.encode())
for candidate in signatures.split()
)運作語意
逾時與重試
您的管理員會設定介於 1 到 10,000 毫秒之間的裁決逾時(預設為 5,000 毫秒)。此預算涵蓋整個交換過程:連線、TLS 交握、請求與回應。
Anthropic 只會重試一次,在 100 毫秒延遲之後,且僅在連線嘗試失敗時重試。重試共用相同的逾時預算,並帶有相同的 webhook-id 與相同的簽章。一旦您的 AI 安全伺服器已回應,該交換就絕不會重試。
Webhook 失敗
逾時、非 200 狀態碼(包括重新導向)、無法解析或過大的回應主體,以及無法連線的端點,全都是 webhook 失敗。webhook 失敗絕不會變成拒絕;而是由您組織的失敗處理設定決定受影響的請求是被封鎖,還是在未經檢查的情況下繼續進行。
斷路器
可歸因於您的 AI 安全伺服器的持續 webhook 失敗會觸發「circuit breaker」(斷路器),停止強制執行:Anthropic 停止聯繫您的伺服器,並對每個請求套用失敗處理。
從觸發後 10 分鐘開始,Anthropic 會測試您的伺服器是否已恢復:最多約每分鐘一次,將一個由您組織自身流量承載的請求傳遞至您的伺服器進行檢查,其簽章與結構與任何其他請求相同。請正常回應它。有效的裁決(允許或拒絕)會重設斷路器並恢復強制執行。webhook 失敗則會讓斷路器維持觸發狀態,並繼續測試。無論哪種情況,測試請求本身都會為其使用者繼續進行:其裁決不會被強制執行,且失敗的測試不會封鎖它,即使在 Block the request 設定下亦然。管理員也可以隨時重設斷路器,而管理員的設定變更會停止自動測試;請參閱斷路器。
每次觸發都會在活動摘要中記錄為一個 inference_hooks_circuit_breaker_tripped 活動,每次觸發一個活動。在斷路器觸發期間,不會記錄任何逐請求的 Inference hooks 活動,因此觸發活動是摘要中對觸發期間的唯一記錄。
延遲
強制執行會將您的 AI 安全伺服器的往返時間加到您組織中每個受管控請求的「latency」(延遲)上。請保持裁決快速,並在向大型組織推出之前對您的伺服器進行負載測試。
來源 IP 位址
傳送至您的 AI 安全伺服器的請求源自 160.79.106.0/24,屬於 Anthropic 已發布的對外 IP 範圍的一部分。請將該區段加入允許清單,而非同一頁面上的對內範圍,後者並未涵蓋它。允許清單可縮小您伺服器的暴露面,但它不能取代簽章驗證:該區段承載的 Anthropic 對外流量不僅限於 Inference hooks。
向前相容性
此協定會在不破壞正確撰寫之伺服器的前提下擴充。您的伺服器必須忽略:
- 提示框架上未知的頂層欄位。
metadata中未知的鍵。- 新的
source.application值。 - 新的
actor.type值。actor是以type區分的聯集,而"user"是目前唯一傳送的種類;未來的種類僅保證type存在。 - 具有無法辨識之
type的內容區塊。
絕不要因為無法辨識的區塊類型或欄位而拒絕請求;請讀取您認識的欄位並略過其餘部分。
未來會引入其他 hook 事件類型。新的事件類型是您的伺服器無法透過略過欄位來處理的新增項目:該請求仍然需要裁決。當頂層 type 是您無法辨識的值時,請回傳允許裁決而非錯誤狀態碼;錯誤回應是 webhook 失敗,而持續的失敗會觸發斷路器。
設計您的整合
正式環境的 AI 安全伺服器除了線路協定之外,還需做出幾項設計選擇。
以 webhook-id 去除重複。 webhook-id 標頭在每次傳遞中都是唯一的,且等於主體的 request_id,而連線失敗的重試會重複使用它,因此它可作為冪等鍵。如果您記錄裁決,請以它作為記錄的鍵。
記錄裁決並關聯拒絕。 儲存您回傳的每個裁決及其 reference_id。每次拒絕都會記錄為一個 inference_hooks_request_denied 合規活動,帶有您的伺服器所回傳的 reference_id,因此您可以將活動摘要中的拒絕與您自己系統中對應的記錄關聯起來。
以一律允許的伺服器進行封存。 若要即時擷取逐字稿而不加以管制,請無條件回傳 {"action": "allow"},並在回應後保存該框架。這是輪詢 Compliance API 的推送式替代方案,而在保存之前先回應可讓您的往返時間不落在使用者的關鍵路徑上。
為終端使用者撰寫 deny_reason。 您回傳的文字就是使用者在其請求被封鎖時所看到的內容,截斷於 500 個字元。請告訴他們該變更什麼,例如應移除哪一類內容,而非輸出只有您的團隊能解讀的掃描器代碼。
後續步驟
啟用 Inference hooks、連接並測試您的端點,以及控制強制執行、失敗處理與推出。
Inference hooks 是什麼、裁決往返如何運作,以及何時使用它們。
Was this page helpful?