# Claude Code http hook

Source: https://immiscible.fly.dev/docs/api/post-v1-hooks-claude-code

`POST /v1/hooks/claude-code`

Claude Code's http hook posts its `PreToolUse` or `PermissionRequest` event here, as Claude Code sends it, with the agent key as a Bearer token. The call is decided exactly as the command hook's would be (`tool.call`, with the project, Claude Code's session id and `tool_use_id` as the idempotency key, so the same call asked twice is one decision), and the answer is in the form Claude Code reads: nothing (`{}`) to allow, so Claude Code's own permission settings still decide; `deny` with the reasons; or, for `PreToolUse`, `ask` with the reasons and the approval link. For `PermissionRequest` only a refusal is returned: Immiscible never grants what Claude Code's own dialog would ask. A missing or unknown key, an event it cannot read and any error come back as a 200 refusal, never an error status, because Claude Code lets a call go on when an http hook errors. `immiscible install claude-code --transport http` writes the hook entry; see [the Claude Code fleet pack](https://immiscible.fly.dev/docs/guides/claude-code-fleet.md).

## Authentication

Workspace key. A gateway key (`ask_...`) issued under Keys in the console, sent as `Authorization: Bearer` or `x-api-key`. It binds traffic to a person or service, a team and optionally a task class.

## Request

curl:

```bash
curl -X POST "https://immiscible.fly.dev/v1/hooks/claude-code" \
  -H "authorization: Bearer $IMMISCIBLE_KEY" \
  -H "content-type: application/json" \
  -d '{
    "hook_event_name": "PreToolUse",
    "session_id": "9f1c2e",
    "cwd": "/Users/dev/acme",
    "tool_name": "Bash",
    "tool_input": {
      "command": "git push --force origin main"
    },
    "tool_use_id": "toolu_01"
  }'
```

Node:

```ts
const res = await fetch('https://immiscible.fly.dev/v1/hooks/claude-code', {
  method: 'POST',
  headers: {
    authorization: `Bearer ${process.env.IMMISCIBLE_KEY}`,
    'content-type': 'application/json',
  },
  body: JSON.stringify({
    hook_event_name: 'PreToolUse',
    session_id: '9f1c2e',
    cwd: '/Users/dev/acme',
    tool_name: 'Bash',
    tool_input: {
      command: 'git push --force origin main',
    },
    tool_use_id: 'toolu_01',
  }),
});
const data = await res.json();
console.log(res.status, data);
```

Python:

```python
import os
import requests

res = requests.post(
    "https://immiscible.fly.dev/v1/hooks/claude-code",
    headers={
        "authorization": f"Bearer {os.environ['IMMISCIBLE_KEY']}",
        "content-type": "application/json",
    },
    json={
        "hook_event_name": "PreToolUse",
        "session_id": "9f1c2e",
        "cwd": "/Users/dev/acme",
        "tool_name": "Bash",
        "tool_input": {
            "command": "git push --force origin main",
        },
        "tool_use_id": "toolu_01",
    },
)
print(res.status_code, res.json())
```

## Response

```json
{ "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny", "permissionDecisionReason": "Immiscible refused: Coding agent baseline refuses a force push to main, master or a release branch, which rewrites history others share." } }
```
