Claude Media
Claude CodeのHooksをHTTPエンドポイントで受ける

Claude CodeのHooksをHTTPエンドポイントで受ける

type: httpのHooksは、イベントのJSONをPOSTで送りレスポンスボディで結果を返します。設定項目とレスポンスの成否判定、コマンド型との違いを扱います。

Hooksの基本設定はシェルコマンドを走らせるcommandタイプが中心ですが、HooksにはほかにHTTPエンドポイントを直接呼び出すtype: "http"もあります。イベントのJSONをPOSTのリクエストボディとして送り、レスポンスボディから許可・拒否・コンテキスト追加といった結果を受け取ります。この記事では設定項目、認証ヘッダーの書き方、レスポンスの成否判定、コマンド型Hooksとの違いを扱います。

HTTP hookとは何か

HTTP hookとは、Hooksのイベントが発火したときにシェルコマンドではなくHTTP POSTリクエストを送る設定タイプです。常駐する検証サービスや、社内の承認システムと連携したいときに使います。コマンド型のようにローカルのプロセスを毎回起動する必要がなく、既に立てているサーバーへイベントを送るだけで済みます。

Claude Codeはイベントの共通入力フィールドをリクエストボディとしてContent-Type: application/jsonで送信します。レスポンスボディは、コマンド型Hooksの標準出力と同じJSON形式で読み取られます。

HTTP hookを設定する

type: "http"のHookは、共通フィールドに加えてurlheadersallowedEnvVarsの3つを使います。

フィールド必須説明
url必須必須説明POST先のURL
headers必須任意説明追加のHTTPヘッダー(key-value)
allowedEnvVars必須任意説明ヘッダー値への環境変数補間を許可する変数名の一覧
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "http",
            "url": "http://localhost:8080/hooks/pre-tool-use",
            "timeout": 30,
            "headers": {
              "Authorization": "Bearer $MY_TOKEN"
            },
            "allowedEnvVars": ["MY_TOKEN"]
          }
        ]
      }
    ]
  }
}

matcherによるツールの絞り込み、ifによるBashサブコマンドの絞り込みは、コマンド型Hooksとまったく同じ規則で動きます。timeoutの既定値もコマンド型と同じで、多くのイベントは600秒、UserPromptSubmitのような一部イベントは30秒です。

環境変数をヘッダーに埋め込む

headersの値は$VAR_NAMEまたは${VAR_NAME}の書式で環境変数を参照できます。ただし補間されるのはallowedEnvVarsに列挙した変数だけです。列挙していない変数への参照は空文字列に置き換わります。認証トークンをハードコードせず、環境変数経由で渡すための仕組みです。

さらに、Claude Codeの設定リファレンスにあるhttpHookAllowedEnvVarsをどこかのsettingsレベルで定義すると、その一覧がすべてのHTTP hookに対する全体的な許可リストとして働きます。個別のHookのallowedEnvVarsと、settings全体のhttpHookAllowedEnvVarsは両方とも満たす必要があります。組織で送信先URLそのものを制限したい場合は、同様にallowedHttpHookUrlsをsettingsに定義します。定義した時点で、そのマージ済みallowlistに一致するURLしか呼び出せなくなります。この2つの許可リストは、管理ポリシー設定を含むどのソース由来のHTTP hookにも等しく適用されます。

レスポンスを返す — 成功・エラーの判定基準

HTTP hookはコマンド型と違い、終了コードではなくHTTPステータスコードとレスポンスボディで結果を判定します。

レスポンス扱い
2xx + 空ボディ扱い成功(終了コード0・出力なしと同じ)
2xx + JSONオブジェクト扱いコマンド型と同じJSON出力スキーマで解釈
2xx + それ以外(プレーンテキスト等)扱い非ブロッキングエラー扱い。テキストはClaudeのコンテキストに追加されない
非2xx扱い非ブロッキングエラー扱い。実行は続行
接続失敗扱い非ブロッキングエラー扱い。実行は続行
タイムアウト扱いコールバックがキャンセルされる

コマンド型Hooksは終了コード2を返すだけでツール呼び出しをブロックできますが、HTTP hookはステータスコードだけではブロックできません。ツール呼び出しを止めたい、あるいは権限を拒否したい場合は、2xxのレスポンスにJSONボディを載せ、その中でpermissionDecision: "deny"のような決定フィールドを指定します。

{
  "hookSpecificOutput": {
    "hookEventName": "PreToolUse",
    "permissionDecision": "deny",
    "permissionDecisionReason": "外部審査サービスが承認していません"
  }
}

エンドポイント側の最小実装イメージ

PreToolUseイベントを受けて.envファイルへの書き込みだけを拒否する、最小限のNode.js実装イメージです。実際のWebフレームワークに合わせて調整してください。

import express from "express";
const app = express();
app.use(express.json());
 
app.post("/hooks/pre-tool-use", (req, res) => {
  const { tool_name, tool_input } = req.body;
  const filePath = tool_input?.file_path ?? "";
 
  if (tool_name === "Write" && filePath.endsWith(".env")) {
    return res.status(200).json({
      hookSpecificOutput: {
        hookEventName: "PreToolUse",
        permissionDecision: "deny",
        permissionDecisionReason: "Cannot modify .env files"
      }
    });
  }
  return res.status(200).json({});
});
 
app.listen(8080);

判定を伴わないイベントは、return res.status(200).json({})のように空オブジェクトの2xxレスポンスを返せば十分です。エンドポイント側で例外が起きてステータスが5xxになった場合でも、Claude Code側は非ブロッキングエラーとして処理を続けるため、サービスの一時的な不調がセッション全体を止めることはありません。

matcherとifはコマンド型と同じ規則で絞り込む

HTTP hookでも、対象を絞る仕組みはコマンド型と共通です。matcherはツール名などイベントごとの対象フィールドに対して評価され、英数字と一部記号だけで構成した値は完全一致、それ以外の文字を含む値は正規表現として評価されます。PreToolUsePostToolUseのようなツール系イベントでは、さらにifフィールドで権限ルール構文による絞り込みを重ねられます。

{
  "type": "http",
  "if": "Bash(git push *)",
  "url": "http://localhost:8080/hooks/git-push-review",
  "allowedEnvVars": ["REVIEW_TOKEN"]
}

この例ではBashツールのうち、git pushで始まるサブコマンドのときだけHTTP hookが呼ばれます。ifは1つのHookハンドラーにつき1つの権限ルールしか持てないため、複数条件を組み合わせたい場合は条件ごとにハンドラーを分けてhooks配列に並べます。

HTTP hookが使えないイベントもある

すべてのイベントでHTTP hookが使えるわけではありません。SessionStartSetupcommandタイプとmcp_toolタイプだけをサポートし、HTTP hookは使えません。セッション開始のたびに走る性質上、ネットワーク往復を前提にすると起動が遅くなりやすいことが理由と考えられます。

WorktreeCreateではコマンド型とHTTP型で結果の返し方が変わります。コマンド型はワークツリーのパスをそのまま標準出力に印字しますが、HTTP hookはhookSpecificOutput.worktreePathというJSONフィールドでパスを返します。ワークツリー生成に関わるHookを両方のタイプで実装する場合は、この違いを踏まえて分岐させる必要があります。

どのイベントでHTTP hookが向くか

HTTP hookは、判定に外部の状態を必要とするイベントで特に効果を発揮します。PreToolUseで社内の承認システムに問い合わせてから危険なコマンドを止める、PostToolUseで編集内容を静的解析サービスへ送って結果をログに残す、Notificationで権限プロンプトの通知を社内チャットへ転送する、といった用途です。いずれも「その場のスクリプトだけでは完結しない、外部サービスとの往復が必要な判定」という共通点があります。

同じイベントに複数のHookを登録した場合、コマンド型・HTTP型を問わずすべて並列に実行されます。HTTP hookのエンドポイントが複数のリクエストを同時に受ける前提でサーバーを実装しておく必要があります。1つのツール呼び出しに対して複数のPreToolUse hookが同時にPOSTしてくる状況は珍しくありません。

コマンド型との違い

観点コマンド型(command)HTTP型(http)
実行方式コマンド型(command)毎回プロセスを起動HTTP型(http)常駐サービスへPOST
結果の伝え方コマンド型(command)終了コード + stdoutHTTP型(http)HTTPステータス + レスポンスボディ
単独でのブロックコマンド型(command)終了コード2だけで可能HTTP型(http)ステータスコードだけでは不可、JSONボディが必須
認証情報の扱いコマンド型(command)環境変数をプロセスが直接読むHTTP型(http)ヘッダーへの環境変数補間(allowedEnvVars)
想定用途コマンド型(command)ローカル完結の検証・整形HTTP型(http)外部サービス・承認システムとの連携

コマンド型は起動のたびにプロセスを立ち上げるため、重い初期化を伴う検証には向きません。HTTP hookは常駐サーバーに投げるだけなので、モデル評価やデータベース照会のような重い処理を伴う判定に向いています。反対に、ローカルのファイル操作や単純なlint実行のような軽い処理では、HTTPサーバーを立てる分だけ構成が複雑になります。

よくあるつまずき

ブロックしたいのに素の403500を返しているだけ、というつまずきをよく見かけます。HTTP hookはステータスコードだけではブロックを表現できません。2xxを返した上でJSONボディにpermissionDecision等の決定フィールドを含める必要があります。

headersにトークンを書いたのに空文字列で届く場合は、allowedEnvVarsへの列挙漏れが原因であることがほとんどです。ヘッダー値の変数参照は、Hook個別のallowedEnvVarsとsettings全体のhttpHookAllowedEnvVarsの両方を満たさないと補間されません。

エンドポイントの応答が遅く、指定したtimeoutを超えるとHookはキャンセルされ、出力はそのまま破棄されます。判定に時間がかかる処理は、レスポンスを返す前にタイムアウト時間内で完了するよう設計するか、timeoutを長めに設定してください。

社内ネットワーク以外に公開したURLを指定する場合は、allowedHttpHookUrlsで送信先を固定し、想定外のURLへ書き換えられても呼び出されない状態にしておくと安全です。個人のローカル開発で完結する用途であれば、http://localhost宛てのURLだけで足ります。

よくある質問

HTTP hookとMCP tool hookはどう使い分けますか

MCP tool hookは、Claude Codeが接続済みのMCPサーバー上のツールを呼び出す形式です。すでにMCPサーバーとして公開している機能を判定に使うならMCP tool hookが自然です。MCPサーバーとして公開していない、任意のWebサービスやSaaSと連携したい場合はHTTP hookのほうが直接的です。

組織全体でHTTP hookの送信先を制限できますか

できます。allowedHttpHookUrlsをいずれかのsettingsレベルで定義すると、そのマージ済みallowlistに一致するURLを持つHTTP hookしか実行されなくなります。管理ポリシー設定を含む、どのソース由来のHTTP hookにもこの制限が等しく適用されます。

レスポンスが2xxでもJSONとして解釈されないことはありますか

あります。ボディがJSONオブジェクトとしてパースできない、あるいはHooksの出力スキーマに合わないJSONを返した場合は、2xxであっても非ブロッキングエラーとして扱われます。エンドポイント側ではContent-Type: application/jsonを明示し、スキーマに沿ったオブジェクトを返すことを確認してください。

認証が無いエンドポイントを指定しても動きますか

動きます。headersallowedEnvVarsも任意項目なので、認証なしのエンドポイントでも設定は成立します。ただしイベントのJSONにはツール名や引数、セッションIDなどの情報が含まれるため、社外からアクセスできるURLを認証なしで指定するのは避けたほうが安全です。

まとめ

HTTP hookは、既存のHTTPサーバーへイベントを送ってHooksの判定を任せる仕組みです。設定はurlheadersallowedEnvVarsの3つが中心で、認証ヘッダーへの環境変数補間には個別HookのallowedEnvVarsとsettings全体のhttpHookAllowedEnvVarsの両方が必要です。コマンド型と違い、ステータスコードだけではブロックを表現できず、2xxレスポンスのJSONボディに決定フィールドを含める必要があります。常駐サービスとの連携や重い判定処理を伴う用途では、毎回プロセスを起動するコマンド型よりもHTTP hookのほうが適しています。

Hooksの全体像と5種類の実行タイプの比較はClaude Code Hooks完全ガイド、コピペで使えるレシピはClaude Code Hooks実例カタログにまとめています。

この記事を共有:XはてブLinkedIn