Claude Webhooks:エージェント主導の外部アラートのための本番パターン4選

Dev.to / 2026/5/16

💬 オピニオンDeveloper Stack & InfrastructureSignals & Early TrendsTools & Practical UsageIndustry & Market Moves

要点

  • Anthropicは2026年5月6日にClaude Managed Agentsの「Webhooks」をパブリックベータとして提供し、エージェントが出来事に応じて外部システムへ署名付きHTTP POSTリクエストを送れるようにした。
  • この記事では本番で使える4つのWebhooksパターンを紹介しており、長時間タスク完了時のSlack通知(HMAC-SHA256による署名検証つき)などが含まれる。
  • 進行を妨げるブロッカーを検知した際にLinearのチケットを自動作成し、重複を防ぐために冪等性キー(idempotency key)で重複発行を抑える方法を示している。
  • PRレビュー完了後にGitHubのIssueコメントを更新するやり方、さらにSLO/コストのイベントを監視基盤へ転送する際に「少なくとも1回(at-least-once)」かつ「ベストエフォート」で配送する考え方を説明している。
  • 著者は、機能が動く一方でドキュメントが薄い現状を踏まえ、本番でWebhooksを組み込んだ経験から得たペイロードの実装上のコツも含めて手順を補っている。
  • Claude Webhooks は 2026 年 5 月 6 日に Managed Agents 向けのパブリックベータを出荷し、エージェントがあなたに ping することを決めたときに HTTP POST を送信します

  • パターン 1 は長時間タスクの完了を、HMAC シグネチャ検証付きで Slack の incoming webhook にルーティングします

  • パターン 2 は、エージェントがブロッカーをフラグしたときに Linear のチケットを自動作成し、冪等性キーで重複排除します

  • パターン 3 は、PR レビューエージェントの完了後に GitHub の issue コメントをパッチし、エージェントコンテキストから取得した issue 番号を使います

  • パターン 4 は、SLO とコストのイベントをカスタム監視エンドポイントへ転送し、配送は少なくとも 1 回(at-least-once)のセマンティクスでベストエフォートとして扱います

私はこの 1 週間、Claude Managed Agents に webhooks を組み込んでいます。そして、機能自体は動くのに、ドキュメントが薄く、実運用で本当に必要なプロダクションパターンの半分が発表記事に載っていない、あの気まずいフェーズにいます。なので、これはその「足りない手作りのマニュアル」です。

Anthropic は 5 月 6 日に SF の開発者向けカンファレンスで、Dreaming、Result Loops、Multi-Agent Orchestration と並んで Managed Agents 向けの Webhooks をパブリックベータとして出荷しました。役立つ整理として、4 つのうち webhooks だけが Claude の外側のシステムと通信するものです。他のものはすべてエージェントループの内側に留まります。Webhooks は、エージェントが「何かが起きた」ことをあなたのスタックの残りに伝える方法です。

以下は、私が実運用で実際に走らせた 4 つのパターンで、初日からこうしたかったと思う注意点(gotchas)とペイロードの形です。

パターン 1: 長時間タスク完了時の Slack ping

最もシンプルなパターンで、ほとんどの人がまずここから始めます。エージェントに 40 分くらいの仕事(データの計算、バッチのリファクタ、リトライ付きのスクレイピング)をやらせます。成功でも失敗でも、とにかく終わった瞬間に #ops に Slack メッセージを送りたい。

event: "task.completed" を使ってエージェント上で webhook を設定し、Slack incoming webhook を指すターゲット URL を指定します。Anthropic はすべてのリクエストに HMAC-SHA256 で署名するので、ペイロードを信頼する前に検証してください。


import crypto from "node:crypto";

export async function POST(req: Request) {
  const raw = await req.text();
  const sig = req.headers.get("x-anthropic-signature") ?? "";
  const ts  = req.headers.get("x-anthropic-timestamp") ?? "";

  const expected = crypto
    .createHmac("sha256", process.env.ANTHROPIC_SIGNING_KEY!)
    .update(`${ts}.${raw}`)
    .digest("hex");

  if (!crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))) {
    return new Response("bad signature", {status: 401 });
  }

  const body = JSON.parse(raw);
  await fetch(process.env.SLACK_WEBHOOK_URL!, {
    method: "POST",
    headers: {"content-type": "application/json" },
    body: JSON.stringify({
      text: `Agent ${body.agent_id} finished task ${body.task_id} in ${body.duration_ms}ms`,
    }),
  });

  return new Response("ok", { status: 200 });
}

Anthropic が送ってくるペイロードは、おおむね次のような形です:

返却形式: {"translated": "翻訳されたHTML"}

{
  "event": "task.completed",
  "delivery_id": "wh_01HXY...",
  "agent_id": "agt_abc123",
  "task_id": "tsk_def456",
  "status": "succeeded",
  "duration_ms": 2417000,
  "result_summary": "...",
  "ts": "2026-05-16T10:42:09Z"
}

ドキュメントが軽く流していることが1つあります。delivery_id は味方です。Anthropic は失敗したデリバリーを指数バックオフで最大 24 時間リトライし、リトライのたびに同じ delivery_id が再利用されます。つまり、Slack ハンドラーは delivery_id で重複排除(デデュープ)すべきです。そうしないと、1つの完了したタスクにつき午前3時に4回も通知が飛びます。

パターン 2: エージェントがブロッカーに当たったら Linear チケットを自動作成する

これは私が週あたり約2時間分を取り戻してくれたパターンです。長いジョブに取り組んでいるエージェントは、ときに先に進めない状態に陥ります。たとえば、認証情報の欠落、要件の曖昧さ、ツールが永続的な 403 を返すケースなどです。エージェントに推測させたり停止させたりする代わりに、task.blocked の webhook を発火させ、それを Linear のチケットに変換します。


const body = await verifyAndParse(req);
if (body.event !== "task.blocked") return new Response("skip", { status:200 });

// 冪等性: エージェントは内部でリトライする可能性があるため、ブロック通知を2回発火するかもしれない
const idemKey = body.delivery_id;

const linearRes = await fetch("https://api.linear.app/graphql", {
  method: "POST",
  headers: {
    "authorization": process.env.LINEAR_API_KEY!,
    "content-type": "application/json",
    "idempotency-key": idemKey,
  },
  body: JSON.stringify({
    query: `
      mutation IssueCreate($input: IssueCreateInput!) {
        issueCreate(input: $input) { success issue { id identifier url } }
      }`,
    variables: {
      input: {
        teamId: process.env.LINEAR_TEAM_ID,
        title: `[Agent blocked] ${body.task_summary}`,
        description: [
          `Agent: ${body.agent_id}`,
          `Task: ${body.task_id}`,
          `Reason: ${body.blocker_reason}`,
          `Last action: ${body.last_action}`,
          ``,
          `Trace: https://console.anthropic.com/agents/${body.agent_id}/runs/${body.run_id}`,
        ].join("
"),
        labelIds: [process.env.LINEAR_LABEL_AGENT_BLOCKED],
        priority:2,
      },
    },
  }),
});

私が身をもって学んだことが2つあります。1つ目は、LinearのGraphQL APIはidempotency-keyをネイティブに考慮してくれないことです。安全のためには、Linearを呼び出す前に、見たdelivery_idの値を小さなKV(Upstash、Redis、あるいはPostgresのテーブルでも)に保存しておく必要があります。2つ目は、エージェントのtask_summaryフィールドは短いのですが、last_actionはLinearの説明が上限を超えてしまうほど長くなる可能性があることです。安全のため8000文字に切り詰めてください。

Managed Agentsがそもそもこれらのブロッカー信号をどのように立ち上げるのか、より広い文脈が知りたい場合は、Multi-Agent Orchestration walkthroughで、オーケストレーターとスペシャリストがハンドオフをどう交渉するかを説明しています。

パターン3:PRレビューエージェントが完了したらGitHubのイシューを更新する

これは、レビューを通じてGitHubのイシュー番号を引き継ぐために、エージェントのコンテキストが必要です。エージェントの実行を作成するときにmetadata.github_issueとして渡し、ClaudeがWebhookペイロード内でそれをそのまま返します。

流れはこうです。PRを開き、ラベルをagent-reviewに設定します。こちら側のWebhookがClaudeのレビューエージェントを起動し、エージェントが発見事項をコメントとして投稿してからreview.completedを発火し、ハンドラーがリンクされたイシューを更新することでループを閉じます。


const body = await verifyAndParse(req);
if (body.event !== "review.completed") return new Response("skip", { status: 200 });

const issueNumber = body.metadata?.github_issue;
if (!issueNumber) return new Response("no issue", { status: 200 });

const verdict = body.result.verdict; // "approve" | "request_changes" | "comment"
const body_md = [
  `Claude review agent finished.`,
  ``,
  `Verdict: ${verdict}`,
  `Findings: ${body.result.findings.length}`,
  `Critical: ${body.result.findings.filter(f => f.severity === "critical").length}`,
  ``,
  `Full report: ${body.result.report_url}`,
].join("
");

await fetch(`https://api.github.com/repos/${process.env.GH_OWNER}/${process.env.GH_REPO}/issues/${issueNumber}/comments`, {
  method: "POST",
  headers: {
    "authorization": `Bearer ${process.env.GH_TOKEN}`,
    "accept": "application/vnd.github+json",
  },
  body: JSON.stringify({ body: body_md }),
});

些細な点ですが、GitHubのREST APIには冪等(idempotency)がありません。そのため、Webhookの配信がリトライされると、コメントが重複して作成されます。私は小さなSQLiteファイルでdelivery_id -> github_comment_idの対応を保持し、リトライ時には既存のコメントを検索して、新しいものをPOSTするのではなくPATCHします。ロジックは5行で、受信箱のノイズを大幅に減らせます。

レビュー・ループ全体の話については、Result Loops pieceで、エージェントの評決をルーブリックで採点するスコアに変換し、そのスコアをPRに対して信頼できるようになるまでの流れを説明しています。

パターン4: カスタムSLOとコスト監視

4つ目のパターンは、特に多数のエージェントを並列で実行している場合に、最も早く回収できるものです。Anthropicは、Webhookペイロード内でイベントごとのコストとレイテンシを公開しているため、それを自分の監視エンドポイントへ振り分けて、コンソールに触れることなく好きなダッシュボードを作成できます。


const body = await verifyAndParse(req);

await fetch(process.env.METRICS_INGEST_URL!, {
  method: "POST",
  headers: { "content-type": "application/json", "x-api-key": process.env.METRICS_KEY! },
  body: JSON.stringify({
    series: [
      { name: "agent.duration_ms", value: body.duration_ms, tags: { agent: body.agent_id, status: body.status }},
      { name: "agent.cost_eur",  value: body.cost_eur,    tags: { agent: body.agent_id, model: body.model } },
      { name: "agent.tokens.in",  value: body.tokens.input,  tags: { agent: body.agent_id } },
      { name: "agent.tokens.out", value: body.tokens.output, tags: { agent: body.agent_id } },
    ],
    ts: body.ts,
    delivery_id: body.delivery_id,
  }),
});

ここで正直に2点あります。

Webhookの配信は、少なくとも1回(at-least-once)のセマンティクスに基づくベストエフォートです。Anthropicは最大24時間リトライしますが、最終的には諦めます。監視の内容が「イベントの100%を確実に叩くこと」に依存しているなら、Webhookだけでは頼れません。バックストップとして、エージェントのAPIから定期的にポーリングして取得する必要があります。私は15分ごとに取得し、突合(reconcile)しています。

Webhookペイロード内のコスト数値は、送信時点での見積もりです。最終的な請求金額へ1時間程度で落ち着く傾向がありますが、長時間実行では3〜7%のズレが生じるのを見たことがあります。社内のダッシュボードならそれで問題ありませんが、クライアントへの請求では日次の使用量エクスポートを待ってください。

エージェントを並列に実行したときに、エージェントのコストがどう積み上がるのかについてのより広い考え方は、「マルチエージェント・オーケストレーション」パートが、実際に支出がどこへ向かっているかを分解して説明しています。

要点

Claude Webhooksは、5月6日の4つの発表の中で最もシンプルで、すでに本番環境でエージェントを運用している人にとっておそらく最も役に立つものです。これらのパターンは奇抜なものではなく、StripeスタイルやGitHubスタイルのWebhookパターンと同じで、HMAC署名と少なくとも1回の配信を備えています。

内面化してほしいことはこれです。エージェントは、あなたのスタックに対して応答してくるシステムになったのであって、ポーリングするブラックボックスではありません。Webhookエンドポイントを、他の本番レシーバと同様に扱ってください。署名を検証し、delivery_idで重複排除(デデュープ)し、リトライを処理するのに十分な状態を保存し、さらに「取り逃せない」イベントについてはバックストップとして定期的なポーリングを用意してください。

Managed Agentsの上に本番ワークフローを構築しているなら、Claude Blueprintには、私が日々使っているプロンプトのパターン、フック、スキルの雛形(スキャフォールディング)がまとめられており、上記のWebhookレシーバ用テンプレートも含まれています。