開發 Inference hooks 整合
建置 AI 安全伺服器,用於接收已簽署的 Inference hooks 請求、驗證這些請求,並回傳允許或拒絕的判定。
Inference hooks 整合是一個「AI security server」(AI 安全伺服器),也就是由 Anthropic 呼叫的 HTTPS 服務。對於每個受管控的請求,您的伺服器會收到一個已簽署的 POST,其中包含「transcript」(對話記錄)。您的伺服器需以允許或拒絕的「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):
# 讀完整個 body;逐字稿可能達數 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 的網路政策會封鎖這類主機。請將您的伺服器託管在您所控制的網域上。關於管理員如何設定及測試 URL,請參閱設定 Inference hooks。
每個請求都會攜帶下列固定標頭。此外,請求也會包含您的管理員所設定的任何自訂請求標頭。當您的組織擁有簽署密鑰後,請求還會包含驗證簽章中所述的 webhook-* 簽章標頭。
| 標頭 | 值 |
|---|---|
Content-Type | application/json |
User-Agent | anthropic-dlp/1 |
Accept-Encoding | identity |
目前只有一種掛鉤事件,也就是提示框架(prompt frame)。每個受管控的推論請求會在推論開始前傳送一次提示框架。Anthropic 會暫停該請求,直到您的 AI 安全伺服器回應或判定逾時為止。
提示框架
請求主體是一個包含下列欄位的 JSON 物件:
| 欄位 | 類型 | 說明 |
|---|---|---|
type | 字串 | 掛鉤事件。目前一律為 "prompt"。未來將推出其他事件類型,因此請妥善處理無法辨識的值(請參閱向前相容性)。 |
request_id | 字串 | 每次推論呼叫的不透明識別碼,用於關聯比對。其值等於 webhook-id 標頭。 |
tenant_id | 字串或 null | 請求所屬組織的不透明識別碼。 |
actor | 物件 | 請求所歸屬的主體,以 type 區分(目前唯一傳送的值為 "user")。包含 id(帶標籤的識別碼,同一帳戶的各請求間保持穩定)和 email_address(若可取得)。id 和 email_address 都可能為 null。 |
source | 物件 | 發起請求的應用程式,包含 application(請參閱來源值)。 |
messages | 陣列 | 截至推論時間點的對話記錄。請參閱內容區塊。 |
session_id | 字串或 null | 不透明的對話識別碼(若存在)。請勿解析此值。對於 Claude Code,這是由用戶端宣告、盡力提供的工作階段識別碼。 |
model | 字串或 null | 此請求的公開模型識別碼(若可取得)。 |
metadata | 物件 | 保留的擴充對應表,鍵與值皆為字串,目前傳送時為空。請勿依賴其中的任何內容,並容許此欄位不存在、存在,或出現任何鍵。 |
請求主體範例:
{
"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 和一個 content 陣列:
role為user或assistant。工具結果會出現在user角色下,與公開 Messages API 的內容模型一致。content陣列由以type區分的區塊組成:
區塊 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、text 區塊的 text,以及 tool_result 區塊的 content 和 is_error。其他欄位在值未知時都可能為 null。例如,圖片會以 attachment 區塊的形式送達,其 file_name 和 text 皆為 null。
若區塊的 type 是您無法辨識的值,代表這是向前相容的新增項目。此類區塊唯一保證存在的欄位是 type。您的政策可以檢查其他存在的欄位,但不得因為無法辨識的類型而拒絕請求。
對話記錄包含的內容
對話記錄是終端使用者所看到的對話,截至推論時間點為止。其中包含:
- 對話記錄文字
- 工具呼叫及其結果
- 擷取的附件文字
- 先前的回合
對話記錄絕不包含系統提示、工具定義、Anthropic 內部上下文、Claude 的隱藏推理或原始檔案位元組。
若某個回合的所有區塊都被排除,該回合會被完全省略。因此,請勿假設使用者與助理的回合會嚴格交替出現。
對話記錄會以未截斷的形式傳送,因此包含大型附件的長對話會產生很大的請求主體。實務上,模型的「context window」(上下文視窗)會讓主體維持在約 10 MB 以下,但協定允許最大 64 MiB。
許多常見的預設值遠小於此上限,例如 nginx 的 client_max_body_size 為 1 MB,Express 的 express.json() 為 100 kB。被拒絕的主體會被視為 webhook 失敗。因此,若失敗處理設定為 Allow the request,過大的提示會在未經檢查的情況下送達模型。
來源值
source.application 是開放式字串,而非封閉的列舉。常見的值包括 claude-ai、claude-code 和 cowork。連線測試和自動斷路器復原檢查則使用 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 | 字串或 null;最多 500 個字元,超出部分會被截斷 | 當 action 為 deny 時顯示給終端使用者;在 allow 時會被忽略。 |
reference_id | 字串或 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 的回應主體,且主體必須未經壓縮。Anthropic 不會跟隨重新導向,也會忽略 cookie。判定主體中的未知欄位會被忽略,因此您可以在本文記錄的欄位之外,回傳內容更豐富的物件。
驗證簽章
請求依照 Standard Webhooks 規範進行簽署,並使用三個標頭。Anthropic 以小寫傳送標頭名稱,但代理伺服器可能會變更其大小寫,因此請以不區分大小寫的方式查找這些標頭。
| 標頭 | 內容 |
|---|---|
webhook-id | 此次傳遞的唯一識別碼,其值等於主體的 request_id。請將其用作冪等性金鑰,以及簽署酬載的第一個組成部分。 |
webhook-timestamp | 請求簽署時的 Unix 時間(以秒為單位),格式為十進位字串。若時間戳記與您伺服器時鐘的差距超過五分鐘(無論提前或落後),請拒絕該請求。 |
webhook-signature | 一個或多個以空格分隔的 v1,<base64> 值,每個值都是對 {webhook-id}.{webhook-timestamp}.{raw body bytes} 計算的 HMAC-SHA256。只要任一值與您計算的結果相符,即可接受請求。比對時請使用常數時間比較。 |
大多數驗證錯誤源自下列兩個細節:
- 驗證原始位元組。 請在進行任何 JSON 解析或重新編碼之前,對收到的原始主體計算 HMAC。
- 使用標準 base64 解碼器解碼密鑰。 簽署密鑰是
whsec_前綴之後的值,以標準 base64 字母表(+和/)編碼,標頭中的簽章也是如此。若密鑰包含+或/(大多數情況下都會包含),URL 安全解碼器會推導出錯誤的金鑰位元組。
當您的組織擁有簽署密鑰後,Anthropic 傳送的每個請求都會經過簽署,包括連線測試,因為設定流程會在第一次測試之前產生密鑰。由於啟用 Inference hooks 需要密鑰,請拒絕任何未簽署的請求。
唯一的例外是:若組織在密鑰成為必要條件之前就已啟用 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()
)
# 比較位元組:對 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 安全伺服器已回應,該次交換就絕不會重試。
Webhook 失敗
下列情況都屬於 webhook 失敗:
- 逾時
- 非 200 狀態碼(包括重新導向)
- 無法解析或過大的回應主體
- 無法連線的端點
Webhook 失敗絕不會變成拒絕。受影響的請求會被封鎖,或在未經檢查的情況下繼續進行,由您組織的失敗處理設定決定。
斷路器
若持續發生可歸因於您 AI 安全伺服器的 webhook 失敗,就會觸發「circuit breaker」(斷路器)並停止強制執行。此時,Anthropic 會停止聯繫您的伺服器,且每個請求都會套用失敗處理設定。
從觸發後 10 分鐘開始,Anthropic 會檢查您的伺服器是否已復原。Anthropic 最多約每分鐘一次,向您的伺服器傳送與 Test connection 相同的合成測試請求(source.application 為 config-test)。此請求與其他請求一樣經過簽署,且不含任何使用者內容。請正常回應此請求:
- 有效的判定(允許或拒絕皆可)會重設斷路器,並恢復強制執行。
- Webhook 失敗會讓斷路器維持觸發狀態,檢查也會持續進行。
管理員也可以隨時重設斷路器,而管理員變更設定會停止自動檢查。請參閱斷路器。
每次觸發都會在活動摘要中記錄為一筆 inference_hooks_circuit_breaker_tripped 活動,每次觸發對應一筆活動。斷路器處於觸發狀態期間,不會記錄任何個別請求的 Inference hooks 活動,因此觸發活動是摘要中關於該觸發期間的唯一記錄。
延遲
強制執行會讓您組織中每個受管控請求的「latency」(延遲)增加您 AI 安全伺服器的往返時間。請讓判定保持快速,並在推廣至大型組織之前,先對您的伺服器進行負載測試。
來源 IP 位址
傳送至您 AI 安全伺服器的請求來自 160.79.106.0/24,此範圍屬於 Anthropic 公布的輸出 IP 範圍。請將此區塊加入允許清單,而非同一頁面上的輸入範圍,因為輸入範圍並未涵蓋此區塊。
加入允許清單可以縮小您伺服器的暴露面,但無法取代簽章驗證,因為此區塊承載的 Anthropic 輸出流量不僅限於 Inference hooks。
向前相容性
協定會持續擴充,但不會破壞正確撰寫的伺服器。您的伺服器必須忽略下列項目:
- 提示框架上的未知頂層欄位。
metadata中的未知鍵。- 新的
source.application值。 - 新的
actor.type值。actor是以type區分的聯集,目前唯一傳送的種類是"user"。未來的新種類僅保證type存在。 type無法辨識的內容區塊。
絕不要因為無法辨識的區塊類型或欄位而拒絕請求。請讀取您認得的欄位,並略過其餘欄位。
未來將推出其他掛鉤事件類型。新的事件類型無法靠略過欄位來處理,因為請求仍然需要判定。當頂層 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?