HTTP Hooks: controls for Claude Code
Connect Claude Code to an HTTP check: POST JSON, decision responses, allowlists and error handling. A local example using synthetic data.
Reading time: 11 min

A Claude Code's HTTP hook sends the context of an event to an endpoint with a POST JSON request. The service can perform a check and return the expected result for that event. It is useful when a rule is already available in a local or shared service, but the decision must comply with the Hooks contract: an HTTP status 403 or 500 does not amount to a single deny. Official HTTP fields and responses.
To block a call through the ordinary path, the service must respond with a decision JSON supported in a 2xx response. A network error, server error, and policy rejection have different meanings. The Guide to the Claude Code Hooks describes the alternatives; here the focus is to make explicit the boundary between service and decision.
The documentation was verified on 9 October 2026. Servers and cases described are instructional examples with synthetic data, not performed in this guide. They do not send information to a public service and do not represent a comprehensive policy for an organization.
In summary
- HTTP input is the JSON of the event, sent with POST.
- The service must return the decision format of the selected event.
- An error status does not automatically constitute a rejection of the tool call.
- Allowed URLs and header variables require explicit configuration.
- The amount of data sent must be proportionate to the control.
When is HTTP a sensible choice?
HTTP can be useful when the team already has a centralized rule or a local service capable of verifying it. For example, a check may know the updated list of operations allowed in a test environment. The endpoint offers the same logic to multiple repositories, while each project decides which events and tools pass in front of it.
If the rule is a comparison of a path and does not need to be shared, a local script can be simpler. Introducing a service involves process to start, port, authentication, monitoring and unavailability management. These elements must solve a concrete need, not be added just because the term webhook seems more modern.
For local protection of routes, PreToolUse for files and commands show the alternative. For a control already offered by a connected tool, Hooks and MCP describes a third path. The choice starts with who owns the rule and where the necessary data is.
Which contract must the endpoint comply with?
The service receives the object of the event and must verify what is needed for its task: expected event, tool name, necessary input and data schema. He should not silently apply the same decision to any request. If the control concerns PreToolUse, the policy output must use the contract of that event. HTTP response handling.
A positive response at the HTTP level means that the request has been handled. The body may contain a decision that rejects the operation. This separation makes the difference between “the service has assessed and denied” and “the service has failed to assess” observable. Use 403 for both confuse transport with hook policy.
Do not insert debugging messages before the response JSON. The content must be readable by the client and belong to the contract. It keeps separate technical logs, avoiding copies of the entire input if they may contain prompts, personal paths or other confidential data.
A local server with a demonstration rule
The following Python 3 server listens only on 127.0.0.1. Deny a call Write on the exact demonstration path and returns an empty object for the other valid inputs. The exact comparison is intentionally limited: does not normalize paths and does not protect a real folder. It is an unexecuted snippet, useful to show the format of the response.
import json
from http.server import BaseHTTPRequestHandler, HTTPServer
class HookHandler(BaseHTTPRequestHandler):
def do_POST(self):
if self.path != "/pre-tool-use":
self.send_error(404)
return
try:
length = int(self.headers.get("Content-Length", "0"))
if length < 1 or length > 65536:
raise ValueError("dimensione non consentita")
event = json.loads(self.rfile.read(length))
if not isinstance(event, dict):
raise ValueError("oggetto richiesto")
except (ValueError, json.JSONDecodeError):
self.send_error(400)
return
output = {}
raw = event.get("tool_input", {})
if (
event.get("hook_event_name") == "PreToolUse"
and event.get("tool_name") == "Write"
and isinstance(raw, dict)
and raw.get("file_path") == "config/protected/demo.json"
):
output = {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Percorso dimostrativo protetto."
}
}
payload = json.dumps(output).encode("utf-8")
self.send_response(200)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(payload)))
self.end_headers()
self.wfile.write(payload)
def log_message(self, format, *args):
return
HTTPServer(("127.0.0.1", 8765), HookHandler).serve_forever()
Body limit and door are choices of example, not recommended values for any project. The absence of authentication is only acceptable for this local demonstration isolated with dummy data; do not turn the snippet into a shared service by exposing it on the network.
How to register the URL in the hook?
The following registration selects Write and sends the call to the demonstration service. An explicit timeout value makes the choice readable, but it must be sized and tested for the project. The service must be started separately. HTTP example in the guide.
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "http",
"url": "http://127.0.0.1:8765/pre-tool-use",
"timeout": 5
}
]
}
]
}
}
Before the test in the session, it sends the server a local fixture with the expected event and synthetic data. Then try a different path and invalid input. Finally, he observes the behavior with the service off. None of these results were collected in this guide: the reader must verify them in his version and in his environment.
Debug of the Hooks helps distinguish a missing recording from an unreachable endpoint. If the HTTP command works out of session but the hook doesn't start, check the settings allowlists as well.
Why can localhost be a different machine?
localhost indicates the environment in which the client runs, not always the user's laptop. In a container, remote session, or cloud environment, the local endpoint may be elsewhere. You need to know where Claude Code runs and where the service is started, before changing port or firewall.
Do not open the server on all interfaces just to solve a connection problem. This changes the perimeter: other processes or other machines could reach it. A shared control service requires authentication, transport protection, and a deliberate access policy. The network must be necessary to the workflow.
The test must describe the environment: client on the computer, service in the same environment or separate service. A connection error does not prove that the policy rule is wrong; it shows that that transport path has not been completed. Keep the two outcomes separate in team messages as well.
How to manage headers and credentials?
HTTP fields allow headers with replacement of environment variables and a list allowedEnvVars. Settings can also narrow variables and URLs with httpHookAllowedEnvVars and allowedHttpHookUrls. Control must take into account the policies actually loaded, not just the individual project file. Allowlist and header.
Do not insert a real token into the shared JSON. Use a dedicated credential, available in the appropriate environment, with limited access to the necessary service. Do not print it to check for interpolation. The test can check that a request is authorized without including the value in the log or text of the reply.
A token does not make the endpoint correct. It is also necessary to know what requests it accepts, what data it stores and who can change the policy. To deepen the organizational level consult Team and Security Mods: Authorization of a component and the execution boundary are separate issues.
HTTP and deny errors: what difference does the team have to see?
The table distinguishes often confusing outcomes. It serves to design service tests, not to presume the behavior of a version that has not been tested. The decision response must be consistent with the event and the current contract. Output of Hooks.
| Result | Meaning | Test to be collected |
|---|---|---|
| 2xx with valid deny JSON | The service evaluated and rejected | Decision and failure to execute the tool |
| 2xx with subject matter without decision | No policy decisions | Next permission stream |
| 500 | Service error | Hook and tool behavior |
| Connection refused | Endpoint unavailable | Technical outcome and observed action |
| Invalid JSON | Broken Response Contract | Parsing error and behavior |
| Timeout | Evaluation not completed on time | Effect in the used version |
The New Option onFailure: "block" is announced by command and HTTP in the release 2.1,295 of 8 October 2026. This verification confirms the introduction, but not the complete scheme for each event and its interactions; for this reason a snippet is not invented that uses it. Before entrusting a binding barrier to such behavior, consult and test the installed version. Official Release.
What data do you send to a shared policy?
The body contains the context of the event. The first question is whether a shared service really needs that material. An identifier may be sufficient to decide on an operation category; more may be needed to analyze a file. Do not automatically transmit transcripts, customer content, or credentials to a service just because it is technically reachable.
Also define retention. Proxy logs, application errors and monitoring tools may retain the body beyond the response. A company-controlled endpoint remains a place where access and data must be managed. Do not call it “internal” as a synonym for “risk-free”.
AI and Data Security offers the context for this choice. If the control can remain local, the advantage is a smaller perimeter; if it is to be shared, it documents the need and the minimum data. The design of the hook includes this information path, not just the URL.
Two control services for Italian SMEs
Hypothetical, untested example: a software house in Bergamo wants to apply the same policy to different test repositories. A service knows the categories of allowed operations. The team prepares synthetic fixtures for positive decision, deny and unavailability. Before linking it to real work it documents what fields it receives, how it authenticates the client and what happens during a failure. It does not consider an HTTP 500 a blocking test.
Hypothetical, untested example: a technical laboratory in Reggio Emilia use a local control for a dummy data importer. Claude Code and the service run on the same computer. The laboratory tests the protected demonstration path and an ordinary path, then turns off the service to observe the error. The demo does not use production data and is not displayed on the network for convenience.
In both cases the policy must have an owner. Changing the service can change the behavior of many projects simultaneously. Deployment of a new version requires evidence and a strategy to identify which rule evaluated a given call.
How to avoid a fragile dependency on service?
A shared control can become a point of unavailability. Define timeouts, data limits, error behavior, and diagnosis. Do not add unlimited automatic retry: they can duplicate work and make it harder to tell if the service received the first request. For controls that modify something, the problem of repetition is even more delicate.
A useful test of unavailability before the team
In an environment with only synthetic data, make a scheduled call while the service is off. Observe technical error, session message, and tool behavior. Repeat with slow response and invalid JSON. The policy must describe these outcomes, in addition to the case where everything works.
Common errors are URLs on the wrong computer, tokens in settings, deny response with error status, full input logging, and an alleged never-tested blocking mode. For checks after an action, PostToolUse remember another limit: the service does not cancel a change already made.
How to recognize the version of the policy that responded?
A central service can change while repositories maintain the same configuration as the hook. The team must be able to know which version of the rule evaluated a request. You can keep this data in an internal synthetic log or in the result provided by your contract, without adding arbitrary fields that Claude Code interprets as decisions.
Before a new distribution, repeat the positive, negative and failure fixtures. Also compare the outcome of a request handled by the previous version: a policy change can make a call first allowed subject to review. The difference must be deliberate and understandable to the projects involved.
A version identifier does not require retaining the full body of the request. The category of operation, outcome and version of the rule may be sufficient for the diagnosis, according to the policy of the service. Separating these metadata from the content reduces the amount of replicated information and makes it easier to read what has changed.
How to bring HTTP Hooks to a maintainable application?
It stores together contract, fixture, data policy and boot procedure. Identifies the version of the Claude Code service and configuration used in the trials. The decision must be readable by those who maintain the software, without having to deduce the meaning from a generic log line.
A dedicated service makes sense when solving a shared rule; Mods by Claude offers a comparison with other forms of customization. Do not add HTTP to replace a simple local function without a need for network or central responsibility.
To build a control connected to a management software, the path of custom software may include contract, authorizations, and fault tests. The result to be achieved is a verifiable decision, with necessary data and known behavior even when the service is not responding.
Giallo Studio guides: methodology and sources
How we apply it in the company
Related resources
Claude Code Hooks: a practical guide in Italian
PreToolUse: Protect files in Claude Code
PostToolUse: formatters and tests in Claude Code
Claude Code Notifications: local hooks and permissions
SessionStart: restore context after compaction
Mods by Claude: what they are and when they are needed in Claude Code
Claude AI for code and development
Mistral vs Llama: comparison between the two open weight modelsFAQ
How does an HTTP hook receive data?
Claude Code sends the event context with a POST JSON request. The service responds according to the format expected for that event.
Does an HTTP 403 equal deny?
No. An error status is not a rejection decision alone. The ordinary path uses a 2xx response with the JSON decision supported.
Can I use localhost?
Yes, if the service is reachable from the environment in which Claude Code runs. In a container, remote desktop or cloud environment localhost can indicate a different machine.
How to manage header tokens?
Use environment variables allowed by the configuration and a dedicated secret with limited access. Do not insert it into shared files or hook logs.
Is the new onFailure option already documented?
The introduction for command and HTTP is confirmed by release 2.1.295. In this verification the complete schema for each event has not been confirmed; a test on the version used is needed.
What data do you send to a shared control?
Only those necessary for the decision. Do not automatically send prompts, transcripts or customer code when the policy can use less sensitive information.