Here is the whole thing in one sentence, citable from the top: **on August 5, 2026, Anthropic shipped inference hooks — a Claude Enterprise beta that sends every employee prompt to an HTTPS server you run for an allow-or-deny verdict before the model ever sees it.** A denied request never reaches Claude. It is the first native, inline control point over what your organization sends into the model, and it covers chat, Claude Code, and Cowork uniformly — with nothing installed on a single laptop.

If you run a company where people paste things into Claude, this is the control you've been building proxies to fake. Here's the mechanism, a working server, and the two settings that decide whether it actually protects you.

What actually changed#

Until yesterday, an enterprise had two ways to govern what left the building for an LLM. You could route all network traffic through a client-side DLP proxy and hope every device stayed on it, or you could use Anthropic's Compliance API to audit conversations after the fact. Neither one can stop a prompt in flight without owning the network.

Inference hooks add the missing piece: a synchronous veto, on Anthropic's side, before inference runs. When a user submits a prompt on a governed surface, Anthropic pauses, POSTs the conversation transcript to your endpoint, and waits for your verdict. Allow, and it runs. Deny, and the user gets a blocked-by-policy message with the reason you supplied. Because the hook fires server-side after the request leaves the client, there's nothing to deploy to devices and no way for an employee to route around it.

The enforcement moved to where the model is. You no longer have to own the network to stop a prompt — you have to answer a webhook in five seconds.

The wire protocol, in one screen#

The contract is deliberately small. Anthropic sends an HTTPS POST with a JSON body. The fields that matter:

{
  "type": "prompt",
  "request_id": "req_abc123",
  "actor": { "type": "user", "email_address": "alice@example.com" },
  "source": { "application": "claude-code" },
  "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", "text": "Q2 revenue grew 14%..." }
    ]}
  ]
}

Your server sees exactly what the user sees: transcript text, tool calls and their results, and extracted attachment text. It never receives raw file or image bytes, system prompts, tool definitions, or Claude's hidden reasoning. There is one event todayprompt, fired once per governed inference request, before inference begins. Response-side enforcement (checking a tool's output before it goes back to the model) is on the roadmap, not shipped.

You reply with HTTP 200 and a verdict. To allow:

{ "action": "allow" }

To block — the deny_reason (≤500 chars) is shown to the user, so write it for them:

{
  "action": "deny",
  "deny_reason": "This prompt appears to contain payment card data, which policy does not allow. Remove the card number and try again.",
  "reference_id": "scan_01HXPT4R9V"
}

One rule that trips people: a non-200 response is a failure, not a deny. If you want to block, you must return 200 with {"action":"deny"}. Throwing a 500 doesn't stop the prompt — it hands the decision to your failure-handling setting (more on that below).

A server that actually enforces#

The minimal working server is about thirty lines. This one verifies the signature (never skip that — an unsigned request isn't from Anthropic) and denies anything that looks like a credit-card number:

import base64, hashlib, hmac, re, time, json
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer

SECRET = "whsec_...".removeprefix("whsec_")
KEY = base64.b64decode(SECRET, validate=True)
PAN = re.compile(r"\b(?:\d[ -]?){13,19}\b")  # your DLP scanner goes here

def signed_by_anthropic(h, body):
    mid, ts, sigs = h.get("webhook-id"), h.get("webhook-timestamp"), h.get("webhook-signature")
    if not (mid and ts and sigs) or abs(time.time() - int(ts)) > 300:
        return False
    want = b"v1," + base64.b64encode(
        hmac.new(KEY, f"{mid}.{ts}.".encode() + body, hashlib.sha256).digest())
    return any(hmac.compare_digest(want, s.encode()) for s in sigs.split())

class Handler(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    def do_POST(self):
        body = self.rfile.read(int(self.headers.get("Content-Length", 0)))
        if not signed_by_anthropic(self.headers, body):
            return self._send({"action": "allow"})  # fail your own way; never crash
        text = json.dumps(json.loads(body)["messages"])
        if PAN.search(text):
            return self._send({"action": "deny",
                "deny_reason": "This prompt appears to contain card data. Remove it and retry."})
        self._send({"action": "allow"})
    def _send(self, verdict):
        out = json.dumps(verdict).encode()
        self.send_response(200); self.send_header("Content-Type", "application/json")
        self.send_header("Content-Length", str(len(out))); self.end_headers()
        self.wfile.write(out)

ThreadingHTTPServer(("", 8000), Handler).serve_forever()

Put a real scanner where the regex is — the point of the webhook is that it's vendor-neutral. You can point it at the same server your existing tools already report to (Netskope, Zscaler, Proofpoint, Palo Alto Networks) or an AI security server you built in-house. Anthropic sends requests from the egress block 160.79.106.0/24; allowlist that, but don't treat it as a substitute for signature verification.

The two settings that decide whether this protects you#

Turning on a webhook is easy. Getting these two right is the job.

Failure handling. If your server is unreachable, errors, or misses the timeout (5 seconds by default, covering the entire connection-to-response exchange), your org-wide setting decides: block the request or let it proceed uninspected. Fail-closed keeps policy airtight but makes your server a hard dependency sitting in the latency path of every governed prompt. Fail-open keeps Claude working during an outage — at the cost of letting prompts through unscanned whenever your server hiccups.

And here's the trap inside the trap: transcripts run up to 10 MB. A long conversation with big attachments produces a large body. Common defaults reject it — nginx caps bodies at 1 MB, Express at 100 KB — and a rejected body counts as a webhook failure. Under fail-open, that means your biggest, most attachment-heavy prompts — exactly the ones most likely to carry a leaked document — are the ones that sail through uninspected. Raise your body limit to 10 MB on purpose.

Rollout. You don't have to block anyone on day one. Shadow mode observes verdicts on live traffic without blocking anything — run it first and watch what would have been denied. Then dial a rollout percentage, exempt roles that need an exception, and only then flip enforcement on. Sustained failures trip a circuit breaker that stops enforcement until an admin turns it back on, so a bad deploy degrades to your failure-handling mode rather than taking Claude down.

What it is not#

Be honest with yourself about the limits before you sell this internally:

That last one matters for most readers here, because most of you aren't a Claude Enterprise org. The lesson still transfers. The valuable idea isn't the SKU — it's the shape: a synchronous webhook that can veto a model call before it spends tokens or leaks data. If you're building an agent, put one in front of your own model calls. The same governance push that funded a whole agentic-control category this year is now a primitive you can copy in thirty lines.