Cronは「いまがその時刻か」と尋ねます。webhookは「今これが起きた。動け」と言います。エージェントの仕事では、この違いが効きます。受信箱の定期点検と、「Stripeの異議が作られた」「main向けのプルリクエストが開かれた」は別物です。
Hermes Agentのwebhookは、認証済みのHTTP POSTイベントをエージェント実行へ変え、結果を設定済みの配信先へ送ります。うまく使えば、鍵付きで仕事がはっきりした小さな扉です。下手に使うと、インターネットから届くあらゆるJSONを1つの巨大プロンプトで処理しようとする、むき出しのポートになります。
本記事では、ルート設計、送信元に適した認証、文書化されたヘルスチェック、実務的なスモークテストを扱います。localhostの外へ出す前に、/articles/hermes-first-week-memory-and-skillsの最初の1週間の固めと組み合わせてください。
webhookをトリガーにすべきとき
webhookを優先するのは、次の場合です。
- 遅延が問題になる(作成者がまだ頭を切り替えているうちにPRを見る)。
- 送信元がすでにイベントを出している(GitHub、GitLab、Jira、Stripe、社内フォーム)。
- 各イベントを、長い会話ではなく焦点を絞った一つの作業にしたい。
cronを優先するのは、次の場合です。
- ずれを定期的に見たい(「14日以内に切れる証明書はあるか」)。
- 送信元がプッシュできない。
- イベントごとの割り込みより、静かな定期報告が欲しい。
Hermesの公式ガイダンスもこの切り分けです。定期確認はcron、イベント契機の実行はwebhookです。
全体構成
送信元システム(GitHub / GitLab / n8n / カスタムアプリ)
| HTTPS POST + 送信元に適した認証
v
Hermes webhookアダプター(デフォルトポート8644)
| ルート:/webhooks/<name>
v
名前付きルート設定(フィルター + プロンプト + 配信)
v
エージェント実行(承認方針の下のスキル/ツール)
v
設定済みの配信(チャットチャネル、GitHubコメント、またはログ)
n8nは左側に置き、検証と集約の層にできます。フィールドを検証し、不要を落とし、最小のペイロードをHermesへPOSTします。これは/articles/hermes-vs-n8n-choose-by-jobの構成例であり、設定するだけで使える公式のベンダー連携ではありません。n8nがワークフロー内でエージェント結果を必要とするなら、webhookアダプターを同期コールバックとして扱わず、デフォルトのポート8642でbearer認証を使う別のHermes APIサーバーを呼び出します。
セットアップ手順(現行ドキュメントで確認)
上流のwebhookドキュメントが示す経路です。
- webhookプラットフォームを有効化する(
hermes gateway setupまたはWEBHOOK_ENABLED=trueなどの環境変数)。 - すべてのルートにシークレットを設定する。送信元に応じて、GitHubのHMACヘッダー、GitLabのプレーントークンヘッダー、汎用V2のタイムスタンプ付きHMACを使う。
- 設定または
hermes webhook subscribeで名前付きルートを作る(コマンドは現行文書に従う)。 - ヘルスチェック:
curl http://localhost:8644/health - 外部システムを
https://your-host/webhooks/<name>へ向ける - 認証済みのテストペイロードを送り、期待したルート、プロンプト、ツール範囲、配信先を確認する。
文書化されたデフォルトポートは8644です。デフォルトでは、ルートあたり毎分30リクエストまで、1 MB超の本文は拒否です。値を変えた場合は、デフォルトではなく設定した上限を試してください。
静的な設定変更は、入れたリリースが文書化するゲートウェイのライフサイクルに従うことがあります。
hermes webhook subscribeで作った動的ルートは再起動なしで読み込まれ、自動生成シークレットを受けます。どちらでも、ゲートウェイプロセスが意図したプロファイルと環境を見ていることを確認してください。対話シェルでの成功は、デーモンが同じ設定を持っている証拠ではありません。
ルート認証は任意ではない
すべてのルートはシークレットを継承または定義する必要があり、なければアダプターは起動に失敗します。認証はプロバイダーごとに違います。GitHubはX-Hub-Signature-256、GitLabは完全一致のX-Gitlab-Token、独自の送信元はV2タイムスタンプ付きHMACです。送信元認証が示すのは、シークレットの保有者が送ったことだけです。ペイロード内の指示が信頼できることにはなりません。
本番で耐える規則:
- 長いランダムシークレットを作り、シークレットマネージャーか権限を閉めた環境ファイルへ置く。エージェントが気軽に読めるスキルMarkdownには置かない。
- 信頼水準が違うシステム(GitHubアプリ対社内フォーム対パートナーwebhook)ではルートごとのシークレットを優先する。
- 無署名や無効な署名は縁で拒否する。「記録して続行」しない。
INSECURE_NO_AUTHは、一時的なループバック試験だけに使う。0.0.0.0やLANアドレスなど、ループバック以外のバインドと組み合わせると、アダプターは起動を拒否する。
独自の送信元には、Hermesの現行汎用V2方式を使います。X-Webhook-TimestampはUnix秒、X-Webhook-Signature-V2は<timestamp>.<raw-body>の小文字十六進HMAC-SHA256ダイジェストです。Hermesは±300秒の窓の外のタイムスタンプを拒否します。V1は本文だけに署名し、再送保護がないため、新しい送信元では使わないでください(公式webhookセキュリティ契約)。
再現可能な署名付きスモークテスト
support-triageという名前のルートを作ったら、機微でないペイロードをpayload.jsonに置きます。このシェルを始める前に、承認されたシークレット注入の仕組みでWEBHOOK_SECRETをセットしてください。本番シークレットをコマンド履歴に打たないでください。次のNodeコマンドは、鍵をプロセス引数へ展開せず環境から読み、ファイルのバイト列そのものに署名します。
: "${WEBHOOK_SECRET:?inject a disposable route secret before running this test}"
timestamp="$(date +%s)"
signature="$(TIMESTAMP="$timestamp" node -e '
const { createHmac } = require("node:crypto");
const { readFileSync } = require("node:fs");
const hmac = createHmac("sha256", process.env.WEBHOOK_SECRET);
hmac.update(`${process.env.TIMESTAMP}.`, "utf8");
hmac.update(readFileSync("payload.json"));
process.stdout.write(hmac.digest("hex"));
')"
curl --fail-with-body \
-H 'Content-Type: application/json' \
-H "X-Webhook-Timestamp: $timestamp" \
-H "X-Webhook-Signature-V2: $signature" \
--data-binary @payload.json \
http://127.0.0.1:8644/webhooks/support-triage
次に、どちらの署名ヘッダーも付けない場合と、300秒より古いタイムスタンプで繰り返します。どちらも拒否されなければなりません。実シークレットをスクリーンショット、チケット、シェル履歴に貼らないでください。文書化の試験には使い捨てのルートシークレットを使い、あとでローテーションします。
外部に出たwebhookが、攻撃者の文章をターミナル操作可能なエージェントの前へ置けると、遠隔のツール実行リスクになります。認証はイベントを送れる人を制限しますが、認証済みペイロードの文章も敵対的であり得ます。TLSとネットワーク制御を使い、ペイロードを最小化し、ターミナル、ファイル、外部操作のツールを制限または無効化し、実行をホストから隔離してください。承認画面は操作意図のガードレールであり、敵対入力向けのサンドボックスではありません。
ペイロードに入れるもの
エージェントへ生の大量データではなく、契約を送ってください。
{
"event_type": "github.pull_request.opened",
"repo": "acme/api",
"pr_number": 1842,
"title": "Add billing retry worker",
"author": "ada",
"base_ref": "main",
"html_url": "https://github.example.invalid/acme/agent-service/pull/1842",
"task": "Summarize risk for main. List missing tests. Do not approve or merge."
}
使わないフィールドは削ります。巨大なワークフロー一式はコンテキストを浪費し、混乱したツール利用を招きます。「小さく明示的で、タスクがはっきりしたペイロード」は本記事の設計上の推奨であり、公式n8n連携についての主張ではありません。
ルート設計:小さな扉を多く
/webhooks/everythingは作らないでください。フィルターとプロンプトを持つ名前付きルートを作ります。
| ルート名 | ソース | 仕事 | 配信 |
|---|---|---|---|
gh-pr-opened | GitHubでPRが開かれた | リスク要約+テストの欠け | 開発のTelegramトピック |
stripe-dispute | Stripeの異議が作られた | チェックリストの下書き | 財務Slack+ログ |
support-form | 検証後のn8n | 分類+返信下書き | 設定済みの非公開Slackチャネル |
uptime-alert | 監視用webhook | 最近のデプロイ文脈を集める | オンコールチャネル |
各ルートは次に答えるべきです。
- どのイベントを受け入れるか?
- 単一の期待出力は何か?
- このルートのエージェントプロファイルで、どのツールを許すか?
- 結果はどこへ行くか?
- 失敗時はどうするか(再試行?デッドレター?人を呼び出す?)?
webhookのペイロードには、メール、アカウントID、本文がよく入ります。Hermesへ届く前にフィールドを最小化してください。ルートスキーマに、ルート単位のメモリ書き込みスイッチは文書化されていません。メモリ無効の専用プロファイルか、
memory.write_approvalを有効にした専用プロファイルを使い、何が残るかを試します。エージェントの推論が不要なら、エージェントを走らせず、文書化されたdeliver_onlyモードを使います。
ヘルスチェックと運用性
文書化されたヘルスエンドポイントはhttp://localhost:8644/health(または自分のホスト/ポート)です。用途は次です。
- 有効化後のローカルスモークテスト
- Docker/Kubernetesの準備確認プローブ
- 未認証webhookルートではなく、プライベートなヘルスURLへの外部稼働確認
次も記録します。
- 署名失敗(攻撃の可能性、またはシークレットの誤設定)
- ペイロード検証失敗
- エージェント実行時間とツール承認の拒否
- 下流配信失敗(チャットAPI停止など)
この信号がなければ、「エージェントが不安定だった」が唯一のインシデント報告になります。記録は観測性を上げます。完全で改ざん耐性のある監査証跡が自動的にできるわけではありません。
例:GitHubでPRが開いたら、焦点を絞って実行する
目標: main向けにPRが開いたとき、Hermesは人向けのリスクメモを下書きする。マージ、承認、コメントは、あとで確認済みの配信経路を足すまで行わない。
- ルート
gh-pr-openedを作る。GitHub直配信なら、X-Hub-Signature-256を検証する共有シークレットを設定する。汎用のn8n中継なら、V2タイムスタンプ付きHMAC契約を実装する。 pull_request/opened/basemainに絞る。- プロンプト契約:目的、影響範囲、欠けているテスト、ロールアウトのリスクを要約する。不明点には印を付け、マージ指示は出さない。
- ツール:設定されていればGitHubを読み取り専用で取得。シェルは無効または承認必須。
- 配信:社内チャネルへMarkdownを投稿し、人が次を決める。
良いエージェント出力の形(構造例。モデルの文言は変わります):
PR #1842:課金リトライワーカーを追加(adaからmainへ)
事実
- 課金ワーカーとキュー設定に触れる(提供されたタイトル/ファイル一覧から)。
- リンクURL:`https://github.example.invalid/acme/agent-service/pull/1842`(例示)
リスク
- バックオフ欠如による再試行の集中 [inference; verify in diff]
- タイトルに冪等性キーへの言及なし [unclear]
確認すべき欠けテスト
- 重複配信/ポイズンメッセージの挙動
- 再試行予算が尽きたときのアラート
このメモからマージしない。人による確認が必要。
停止条件付きの、イベント駆動エージェント実行です。自律的なコードオーナーではありません。
演習:一つを有効にする前に、三つのルートを設計する
紙(または運用手順書)に、自分たちの構成向けのwebhookルートを三つ書きます。それぞれ埋めてください。
- 名前
- ソース+イベントフィルタ
- 認証方式とシークレットの所有者
- ペイロードのフィールド:演習では意図して小さく、最大10個から始める
- プロンプト:最大8行から始め、そのルートの評価が求めるものだけ足す
- 許可ツール
- 配信先
- 失敗時の挙動
まず最もリスクの低いルートだけ実装します。通例は社内アラートか、下書きだけのPR要約です。curlで/healthを確認し、認証済みのテストPOSTと無効認証の試験、それから非本番リポジトリかステージングで実イベントを1つ、の順です。
想定すべき失敗モード
- ローテーション後のシークレット不一致: 認証が失敗する。ノートPCのシェルだけでなく、ゲートウェイプロセスが使う環境を直す。
- 広すぎるプロンプト: エージェントがツールを即興する。ルートを分ける。
- 再試行の集中: 送信元がPOSTを繰り返す。Hermesは配信IDを1時間キャッシュしますが、意味のある重複排除には安定した
X-GitHub-DeliveryまたはX-Request-IDが要ります。顧客に見える操作には、再送され得る期間に保持が合う、業務上の永続的な冪等性も必要です。 - メモリ汚染: 大量アラートが永続メモリに入る。専用プロファイルと明示的なメモリ設定を使う。
- むき出しのポート: ヘルスとwebhookが、意図したTLSとネットワーク制御なしで届く。ツールを足す前にネットワークを直す。
開いておきたい参照
- Hermes webhookアダプター
- Hermes APIサーバー
- Hermesセキュリティモデル
- Hermesドキュメント
- 内部:/articles/first-ai-agent-in-n8n、/articles/hermes-vs-n8n-choose-by-job
イベント駆動エージェントは、各ルートが狭く、認証され、観測可能で、設定済みの配信先を持つときに元が取れます。webhookアダプターはイベント受信の玄関であり、bearer認証の要求/応答APIではありません。その前提で設計し、試してください。



