シェルスクリプトを書かなくても、URLを1行指定するだけでClaudeのツール実行をSlackに通知したり、社内の承認サーバーで制御したりできる。2026年3月3日にリリースされたClaude Code v2.1.63 で追加されたHTTP Hooksの話だ。
command hooksと何が違うのか
既存の command フックはシェルコマンドを実行する。コードレビュー前に自動フォーマットを走らせたり、.envファイルへのアクセスをブロックしたりするのに使う。便利だが、スクリプトファイルは各マシンに置かれる。チーム全員が同じロジックを使うには、スクリプトをリポジトリに含めた上で、各自がセットアップする必要がある。
http フックはここを変える。シェルスクリプトの代わりに、URLへのHTTP POSTでイベントデータを送る。Claude Codeが何かをするたびに、指定したサーバーへリクエストが飛ぶ。ロジックはサーバー側にあるから、チーム全員が同じURLを向けるだけで済む。
SlackにGitHub連携を入れると、PRを作るたびに通知が来る。あの仕組みとまったく同じ発想だ。Claude CodeがコードをEditするたびに、自分のサーバーに通知が飛ぶ。
設定の書き方
公式ドキュメントによれば、HTTP hookは設定JSONを直接書く必要がある。/hooks インタラクティブメニューはcommand hookの追加にしか対応していないためだ。
基本的な構造はこうなる:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "https://your-server.example.com/hooks/pre-tool-use",
"headers": {
"Authorization": "Bearer $HOOK_SECRET"
},
"allowedEnvVars": ["HOOK_SECRET"],
"timeout": 30
}
]
}
]
}
}
HTTP hook固有のフィールドは3つだ:
| フィールド | 必須 | 説明 |
|---|---|---|
url | yes | POST先のURL |
headers | no | カスタムHTTPヘッダー($VAR_NAMEで環境変数を展開) |
allowedEnvVars | no | 展開を許可する環境変数名のリスト |
headers の環境変数展開には注意が要る。allowedEnvVars に列挙した変数名だけが展開される。未記載の変数名は空文字列に置き換えられ、エラーにはならない。認証トークンを使う場合は必ずこのリストに追加すること。
Claude Codeは、フックのイベントデータを Content-Type: application/json でPOSTする。受け取るJSONには session_id、cwd、hook_event_name などの共通フィールドに加え、イベント固有のフィールド(PreToolUse なら tool_name と tool_input など)が含まれる。
レスポンスの読み方——「ブロック」には2xxが必要
ここがHTTP hookの最大の落とし穴だ。公式ドキュメントのHTTP response handlingによれば、Claude Codeはサーバーのレスポンスを次のように解釈する:
- 2xxで空のbody:成功。実行を続ける
- 2xxでテキストbody:テキストがClaudeのコンテキストに追加される
- 2xxでJSONbody:command hookと同じJSONスキーマで解析される
- 非2xxステータス:非ブロッキングエラー。実行は続行される
- 接続失敗・タイムアウト:非ブロッキングエラー。実行は続行される
// ❌ これだけではブロックできない
// HTTP 403 Forbidden
// {}
// → 非2xxなので非ブロッキングエラーとして扱われ、実行は続行される
// ✅ ブロックするには2xxでJSONを返す
// HTTP 200 OK
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "このコマンドは承認されていません"
}
}
HTTPステータスコードだけでは動作をブロックできない。403や500を返しても実行は続行される。ブロックしたい場合は、2xxのレスポンスに permissionDecision: "deny" か decision: "block" を含むJSONを返す必要がある。
サーバーがダウンしていたり、タイムアウトしたりしても、Claude Codeはエラーを無視して実行を続行する。セキュリティ上「通らせてはいけない」操作をHTTP hookで守ろうとする場合、サーバーの可用性を確保するか、command hookと組み合わせるかの設計判断が必要になる。
実装例①:承認サーバーでBashコマンドを制御する
最も実用的な使い方の一つが、PreToolUse に承認サーバーを繋げるパターンだ。「本番データベースへのアクセスには承認が要る」というチームポリシーを、スクリプトファイルではなくサーバー側で一元管理できる。
設定ファイル(.claude/settings.json):
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "http",
"url": "https://approvals.your-team.example.com/hooks/pre-tool-use",
"headers": {
"Authorization": "Bearer $HOOK_SECRET"
},
"allowedEnvVars": ["HOOK_SECRET"],
"timeout": 30
}
]
}
]
}
}
承認サーバー側(Node.js/Express の例):
// approvals-server.js
app.post('/hooks/pre-tool-use', (req, res) => {
const { tool_name, tool_input } = req.body;
// 本番DBへの接続コマンドをブロック
if (tool_name === 'Bash' && tool_input?.command?.includes('prod-db-host')) {
return res.json({
hookSpecificOutput: {
hookEventName: 'PreToolUse',
permissionDecision: 'deny',
permissionDecisionReason: '本番DBへのアクセスは承認が必要です。#ops-approvals で申請してください'
}
});
}
// それ以外は通過させる
res.status(200).json({});
});
command hookで同じことをやろうとすると、各開発者のマシンにスクリプトが要る。HTTP hookなら、全員が同じURLを向けるだけでチームポリシーを一元管理できる。ポリシーを変えたいときもサーバー側のロジックをひとつ変えるだけだ。
実装例②:コード変更をSlack通知する(中継サーバー経由)
PostToolUse にSlack Incoming Webhookを直接指定できないか——と考えた人は多いはずだが、Slack Incoming Webhooksが期待するJSON形式は {"text": "メッセージ"} であり、Claude CodeがPOSTするJSONとはまったく異なる。直接接続はできない。
現実的な構成は、中継サーバー(Cloudflare WorkersやVercel Edge Functionsで十分)を間に挟む形だ:
Claude Code → 中継サーバー(JSON変換) → Slack Incoming Webhook
中継サーバーの例(Cloudflare Workers):
// worker.js
export default {
async fetch(request, env) {
const body = await request.json();
const { tool_name, tool_input, cwd } = body;
// Claude CodeのJSONをSlack形式に変換
const slackPayload = {
text: `[${tool_name}] ${tool_input?.file_path || tool_input?.command || '(no detail)'} in ${cwd}`
};
await fetch(env.SLACK_WEBHOOK_URL, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(slackPayload)
});
return new Response('{}', { status: 200 });
}
};
設定ファイル:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "http",
"url": "https://your-worker.workers.dev/slack-notify",
"timeout": 10
}
]
}
]
}
}
PostToolUse はツール実行後のフックなので、ここではブロックはできない。通知・ログ・監視などの「後処理」に使う。
実装例③:セッション終了でCI/CDをトリガーする
Stop フックをGitHub Actionsの repository_dispatch に繋げると、「Claudeが作業を終えたら自動でCIを走らせる」が実現できる。
GitHub APIが期待する形式は {"event_type": "...", "client_payload": {...}} だが、Claude CodeがPOSTするJSONはそれと異なる。こちらも中継サーバーが必要になる。
設定ファイル側の概念は次の通りだ:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "http",
"url": "https://your-relay.example.com/trigger-ci",
"headers": {
"Authorization": "Bearer $RELAY_TOKEN"
},
"allowedEnvVars": ["RELAY_TOKEN"],
"timeout": 30
}
]
}
]
}
}
中継サーバー側でGitHub APIへのリクエストを組み立て、repository_dispatch イベントを発火させる。Stop フックは stop_hook_active フィールドを持っており、すでにフックの結果でセッションが継続されている場合は true になる。無限ループを防ぐためにこのフィールドを確認するのが安全だ。
HTTP hookが使えるイベント
すべてのイベントがHTTP hookに対応しているわけではない。公式ドキュメントによれば、http フックが使えるのは次の8イベントだ:
PermissionRequest、PostToolUse、PostToolUseFailure、PreToolUseStop、SubagentStop、TaskCompleted、UserPromptSubmit
SessionStart、Notification、ConfigChange、WorktreeCreate など残りのイベントは command フックにしか対応していない。
また、同一URLへの並列フックは自動的に重複排除される。同じURLを2箇所に書いても、同じイベント内では一度だけ送られる。
「チームのルールをコードに書かない」という視点
command hookはシェルスクリプトだから、ロジックはスクリプトファイルに書く。HTTP hookのロジックはサーバー上にある。この差は「個人のワークフロー自動化」と「チームのルールの一元管理」の違いだ。
承認フローが変わった。新しい禁止コマンドが増えた。ログの出力先を変えたい——これをリポジトリのスクリプトファイルの変更として全員に展開するのか、サーバー側のロジックをひとつ変えるだけで全員に反映するのか。HTTP hookは後者を可能にする。
Webhookを日常的に使っているチームには、Claude CodeのHTTP hookは自然に馴染む概念だ。シェルスクリプトが書けなくてもいい。サーバーを一本立てて、URLを指定するだけでClaude Codeのイベントを受け取れる。