Skip to main content
Webhooks

Webhook イベントを受信する

Webhook リクエストを検証し、複数回配信される可能性のあるイベントを確実に処理します。

Webhook は現在 Beta 機能です。API、フィールド、および動作は今後のバージョンで変更される可能性があります。

HTTP リクエスト

イベントが発生すると、Qoder Cloud Agents は Endpoint URL に HTTP POST リクエストを送信します。
POST /webhooks/qoder HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: QoderCloudAgents-Webhook/1.0
Webhook-ID: whe_01k1jbxexample
Webhook-Event-Type: forward.schedule_run.succeeded
Webhook-Timestamp: 1788253679
Webhook-Signature: v1,BASE64_HMAC_SHA256
Header説明
Webhook-IDイベント ID。再試行時も同じ値を維持し、コンシューマーの冪等性キーとして使用できます。
Webhook-Event-Typeイベントタイプ。
Webhook-Timestamp署名生成時の Unix 秒タイムスタンプ。
Webhook-Signatureリクエスト署名。現在の形式は v1,<base64>。
任意の 2xx レスポンスが処理成功を示します。署名を検証してイベントを信頼できるキューに書き込み、速やかにレスポンスを返してください。

イベント構造

Forward のビジネスイベントは共通の envelope を使用します。
{
  "type": "event",
  "id": "whe_01k1jbxexample",
  "created_at": "2026-09-01T09:07:59Z",
  "data": {
    "type": "forward.schedule_run.succeeded",
    "source": "tool",
    "source_session_id": "sess_origin",
    "id": "srun_01k1jbrexample",
    "schema_version": 1,
    "run_id": "srun_01k1jbrexample",
    "schedule_id": "sched_01k1jbqexample",
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "status": "completed",
    "trigger_type": "schedule",
    "attempt": 1,
    "initial_events_version": 1,
    "push_status": "succeeded",
    "duration_ms": 15432
  }
}
フィールド型説明
typestring常に event。
idstringWebhook イベント ID。Webhook-ID と一致します。
created_atstringイベント発生日時。RFC 3339 形式。
data.typestringイベントタイプ。Webhook-Event-Type と一致します。
data.idstringイベント対象のリソース ID。管理ツールの検索・失敗イベントでは stcall_* 呼び出し記録 ID。
data.schema_versionintegerイベントデータのバージョン。現在は 1。
今後の拡張との互換性を保つため、認識できない新しいフィールドは無視してください。

Schedule イベント

以下の 5 種類の Schedule イベントの data には、source と source_session_id が含まれます。意味は Schedule の作成元を参照してください。Run イベントは親 Schedule の作成元を継承します。 サービスのデプロイ後に新たにキューに入ったイベントには、常に両フィールドが含まれます。過去のイベントやその再試行では欠落する場合があるため、クライアントはフィールドの欠落に対応してください。必要に応じてスケジュールを取得するまたはスケジュール実行を取得するで作成元を確認できます。

forward.schedule.created

Schedule が正常に作成されたときに送信されます。
{
  "type": "event",
  "id": "whe_01k1jbxcreated",
  "created_at": "2026-09-01T09:00:00Z",
  "data": {
    "type": "forward.schedule.created",
    "source": "api",
    "source_session_id": null,
    "id": "sched_01k1jbqexample",
    "schema_version": 1,
    "schedule_id": "sched_01k1jbqexample",
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "status": "active",
    "trigger_policy_type": "cron",
    "next_trigger_at": "2026-09-02T01:00:00Z"
  }
}
trigger_policy_type と next_trigger_at は、対応する値がある場合にのみ返されます。

forward.schedule.updated

update_forward_schedule ツールが Schedule を実際に変更し、ツール結果が updated の場合に送信します。Forward の組み込み MCP と従来の Custom Tool の両方の呼び出し経路が対象です。HTTP の Schedule 更新 APIを直接呼び出した場合、このイベントは生成しません。
{
  "type": "event",
  "id": "whe_01k1jbxupdated",
  "created_at": "2026-10-27T09:00:00.123456Z",
  "data": {
    "type": "forward.schedule.updated",
    "id": "sched_01k1jbqexample",
    "schema_version": 1,
    "schedule_id": "sched_01k1jbqexample",
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "status": "active",
    "updated_at": "2026-10-27T09:00:00.123456Z",
    "schedule_version": 3,
    "source": "tool",
    "source_session_id": "sess_origin",
    "trigger_policy_type": "cron",
    "next_trigger_at": "2026-10-28T02:00:00Z"
  }
}
フィールド説明
schedule_id変更された Schedule の ID。data.id と同じです。
identity_id、template_idSchedule が属する Identity と Template。
status更新後の Schedule のリソース状態。例えば active または paused。ツール結果の updated ではありません。
updated_at今回実際に保存された更新時刻。UTC RFC 3339 形式で、マイクロ秒精度です。外側の created_at はこの時刻に対応します。
schedule_version今回の更新後の Schedule 設定バージョン。整数です。イベント構造のバージョン schema_version、実行入力のバージョン initial_events_version とは独立しています。
source、source_session_idSchedule の作成元。今回の変更元や Session ではありません。API で作成した場合は api と null、ツールで作成した場合は tool と作成時の Session ID です。
trigger_policy_type、next_trigger_at更新後のトリガーポリシータイプと次回トリガー時刻。対応する値がある場合に返します。
通知には上記のリソース要約のみが含まれ、タスクの指示、完全な初期イベント、実行設定、メタデータ、認証情報は含まれません。従来の Custom Tool 経由では、今回の変更呼び出しを示す tool_call が追加される場合があります。MCP 経由の成功イベントには、この関連付けオブジェクトは含まれません。 Schedule の変更と通知イベントは同じトランザクションで保存されます。変更後の設定が現在の設定と同じ場合、ツールは unchanged を返し、Schedule への書き込みも成功・失敗通知の送信も行いません。呼び出し元は現在の Session でツール結果を確認できます。過去の変更に対する通知は遡って送信されません。 同じ変更の配信再試行では Webhook-ID を再利用します。その後の実際の変更では新しいイベント ID が生成されます。受信側は Schedule ID だけでなく、Webhook-ID で重複排除してください。イベントは変更順に届くとは限りません。同じ Schedule の設定を同期するときは、schedule_version を併用するか最新の詳細を取得し、古いバージョンで新しいバージョンを上書きしないようにしてください。 既存の Endpoint は events に forward.schedule.updated を追加する必要があります。* を購読する Endpoint は自動的に受信します。確定的な変更失敗には forward.schedule.update_failed を使用します。管理ツールの結果を参照してください。このイベントは Schedule Run の実行結果とは独立しています。

forward.schedule.archived

Schedule が未アーカイブ状態からアーカイブ済み状態に初めて変わったときに送信されます。data.status は常に archived です。 次のアーカイブ経路が対象です。
  • アーカイブ API の呼び出し、または Session 内での delete_forward_schedule ツールの呼び出し。
  • 一括アーカイブ。この操作で実際にアーカイブされた Schedule ごとに 1 件のイベントを送信します。
  • 1 回限りのタスクの実行終了後、または stop_at によりトリガープランに次回実行がなくなったときの自動アーカイブ。
  • Identity のクリア時のカスケードアーカイブ、および依存先が利用できなくなったことによるアーカイブ。Schedule を一時停止しただけでは、このイベントは送信されません。
{
  "type": "event",
  "id": "whe_01k1jbxarchived",
  "created_at": "2026-09-22T02:00:00Z",
  "data": {
    "type": "forward.schedule.archived",
    "id": "sched_01k1jbqexample",
    "schema_version": 1,
    "schedule_id": "sched_01k1jbqexample",
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "source": "tool",
    "source_session_id": "sess_origin",
    "status": "archived",
    "archived_at": "2026-09-22T02:00:00Z"
  }
}
フィールド説明
schedule_idアーカイブされた Schedule の ID。data.id と同じです。
identity_id、template_idSchedule が属する Identity と Template。
source、source_session_idSchedule の作成元であり、今回のアーカイブ操作の実行元ではありません。API で作成した場合は api と null、ツールで作成した場合は tool と作成した Session の ID です。
status常に archived。Schedule オブジェクトを取得したときは、引き続き archived_at が null でないことに基づいてアーカイブ状態を判断してください。
archived_atRFC 3339 形式の実際のアーカイブ時刻。外側の created_at はこの時刻に対応します。
アーカイブ状態と通知イベントは同じトランザクションで保存されます。再度アーカイブしても新しいイベントは生成されませんが、配信の再試行により同じイベントが複数回届く場合があります。Webhook-ID で重複を排除してください。過去にアーカイブ済みのデータに対する通知は遡って送信されません。 このイベントは Schedule がアーカイブされたことを示します。既存の Run が終了したことや、Identity のクリアに伴うほかのクリーンアップ処理がすべて完了したことを示すものではありません。アーカイブ、作成、Run 結果の通知間で到着順序は保証されません。ローカル状態を同期するときは、遅れて届いた作成通知でアーカイブ済みタスクを有効な状態に戻さないでください。 これは独立した Webhook 通知であり、Session 内にアーカイブイベントを追加するものではありません。既存の Endpoint は events に forward.schedule.archived を追加する必要があります。* を購読している Endpoint は自動的に受信します。

forward.schedule_run.succeeded

Schedule Run が正常に完了したときに送信されます。data.status は completed です。

forward.schedule_run.failed

Schedule Run が失敗したときに送信されます。構造は成功イベントと同じで、data.status は failed となり、error_type が含まれる場合があります。
{
  "type": "event",
  "id": "whe_01k1jbxfailed",
  "created_at": "2026-09-01T09:07:59Z",
  "data": {
    "type": "forward.schedule_run.failed",
    "source": "tool",
    "source_session_id": "sess_origin",
    "id": "srun_01k1jbrfailed",
    "schema_version": 1,
    "run_id": "srun_01k1jbrfailed",
    "schedule_id": "sched_01k1jbqexample",
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "status": "failed",
    "trigger_type": "schedule",
    "attempt": 1,
    "initial_events_version": 1,
    "push_status": "failed",
    "duration_ms": 1200,
    "error_type": "runtime_error"
  }
}

Schedule Run の入力バージョン

実行入力のバージョンが判明している場合、forward.schedule_run.succeeded と forward.schedule_run.failed は次のフィールドを返します。
フィールド型説明
data.initial_events_versioninteger(任意)この Run の現在の実行試行で選択された入力バージョン。Run 照会レスポンスと一致し、不明な場合は省略します。
このフィールドは、イベント構造を示す data.schema_version とは独立しています。コールバックの再配信では元のイベントの入力バージョンを保持します。後続の Schedule 更新はキュー内のコールバックを変更せず、コールバックに完全な initial_events が追加されることもありません。過去のイベントや入力バージョンを確認できない Run のコールバックでは、このフィールドが欠落する場合があります。受信側は不明として扱い、現在の Schedule のバージョンで補完したり、欠落を 0 と解釈したりしないでください。バージョンと attempt の詳細は Schedule 入力バージョンを参照してください。 業務結果は対応する Schedule Run 取得 API を正としてください。Webhook は状態変更を通知するためのものです。 入力全文が必要な場合は、コールバックの data.run_id を使い、include_initial_events=true を指定して Schedule Run を取得します。その試行の入力がバインド済みで確認できる場合のみ initial_events が返されます。現在の Schedule 設定でその入力を代替しないでください。
curl -s 'https://api.qoder.com/api/v1/forward/schedule_runs/srun_01k1jbrexample?include_initial_events=true' \
  -H "Authorization: Bearer $QODER_PAT"

Schedule 管理ツールの結果

これらの通知は、Forward の組み込み MCP と従来の Custom Tool 経由の Schedule 作成・検索・変更・アーカイブツールを対象とします。管理操作の結果を示し、Schedule Run の実行成功・失敗とは独立しています。
ツール成功イベント失敗または入力が必要なイベント
create_forward_scheduleforward.schedule.createdforward.schedule.create_failed
list_forward_schedulesforward.schedule.queriedforward.schedule.query_failed
update_forward_scheduleforward.schedule.updated。実際に変更があった場合のみforward.schedule.update_failed
delete_forward_scheduleforward.schedule.archivedforward.schedule.archive_failed
作成・変更・アーカイブの成功にはリソースイベントを使用し、2 件目の成功通知は送信しません。検索は一致なしでも成功です。アーカイブは一致なしの場合に失敗し、複数一致では schedule_match_ambiguous を返して具体的な Schedule ID の選択を求めます。候補はアーカイブされません。 作成失敗のすべての状況、エラーコード、経路ごとの違いは、自然言語による作成失敗とエラーコードを参照してください。Webhook のエラーコードは data.error.code から読み取ります。同期ツール結果では場所が異なる場合があります。 変更ツールの結果が updated の場合は forward.schedule.updated を送信し、unchanged の場合は通知しません。確定的な業務失敗、query の一致なし・複数一致では forward.schedule.update_failed を生成し、複数一致の場合はユーザーに具体的な ID の選択を求めます。この失敗イベントは Schedule Run の実行失敗を意味しません。

検索成功の例

{
  "type": "event",
  "id": "whe_01k1jbxqueried",
  "created_at": "2026-10-19T09:00:00Z",
  "data": {
    "type": "forward.schedule.queried",
    "id": "stcall_example_query",
    "schema_version": 1,
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "source": "tool",
    "tool_call": {
      "id": "stcall_example_query",
      "name": "list_forward_schedules",
      "session_id": "sess_origin",
      "invocation_path": "mcp",
      "turn_id": "turn_example"
    },
    "result": {
      "schedules": [
        {
          "schedule_id": "sched_01k1jbqexample",
          "name": "Daily summary",
          "status": "active",
          "trigger_policy_type": "cron",
          "next_trigger_at": "2026-10-20T01:00:00Z"
        }
      ],
      "count": 1,
      "has_more": false,
      "limit": 20
    }
  }
}

アーカイブ対象の選択が必要な例

{
  "type": "event",
  "id": "whe_01k1jbxarchivefailed",
  "created_at": "2026-10-19T09:05:00Z",
  "data": {
    "type": "forward.schedule.archive_failed",
    "id": "stcall_example_archive",
    "schema_version": 1,
    "identity_id": "idn_01k1jbnexample",
    "template_id": "tmpl_01k1jbmexample",
    "source": "tool",
    "tool_call": {
      "id": "stcall_example_archive",
      "name": "delete_forward_schedule",
      "session_id": "sess_origin",
      "invocation_path": "mcp"
    },
    "error": {
      "code": "schedule_match_ambiguous",
      "category": "requires_input",
      "message": "Choose one schedule_id from the matching Schedules.",
      "requires_input": true
    }
  }
}

結果フィールドと関連情報

次の表は検索成功イベントと管理ツールの失敗イベントに適用されます。作成・変更・アーカイブ成功のフィールドは、上記の各リソースイベントの説明を参照してください。
フィールド説明
data.idstcall_* 呼び出し記録 ID。data.tool_call.id と同じです。
data.source常に tool。今回の呼び出しの Session は tool_call.session_id で確認します。
data.tool_call呼び出しの関連情報。id、name、session_id を含みます。MCP 経由では invocation_path=mcp と任意の turn_id も含みます。従来の Custom Tool 経由では source_event_id、tool_use_id または custom_tool_use_id、任意の session_thread_id を含みます。呼び出し元が実際に提供した関連フィールドのみ返します。
data.result検索成功時のみ返し、schedules、count、has_more、limit を含みます。Schedule の要約は ID、名前、状態、トリガーポリシーの要約のみです。next_trigger_at は値がある場合に返します。タスクの指示、完全な実行設定、認証情報は含みません。一致なしでは schedules=[]、count=0 です。
data.error失敗またはユーザー入力が必要な場合に返し、code、category、message、requires_input を含みます。分類は invalid_request、forbidden、not_found、requires_input です。
従来の Custom Tool 経由の作成・変更・アーカイブ成功イベントには、tool_call が追加される場合があります。作成・アーカイブイベントには schedule_version も含まれる場合があり、変更イベントには常にこのバージョンが含まれます。MCP 経由の成功通知はリソースイベント構造を使用し、tool_call は追加しません。リソースイベントの source と source_session_id は引き続き Schedule の作成元を示し、tool_call が今回の管理呼び出しを示します。 結果通知は既定で有効ですが、Endpoint で対応する完全なイベント名を購読する必要があります。* は新しいイベントも受信し、forward.schedule.* などのプレフィックスワイルドカードは使えません。信頼できる呼び出し元を確認できない場合、インフラ障害、コミット結果が不確かな場合は、業務上の失敗通知を保証しません。 保存済みの同一イベントの配信再試行では Webhook-ID を再利用するため、受信側で重複排除に使用します。記録済みの従来の Custom Tool 呼び出しを再生しても、Schedule の変更や新しい成功イベントの生成は繰り返されません。stcall_* は通知の関連付け用であり、上流ツール呼び出しの冪等性キーではありません。個別の MCP リクエストは独立して実行されます。結果が不確かでも、作成・変更・アーカイブを自動で再試行しないでください。管理通知と Run 通知の到着順序にも依存しないでください。

Managed Agents リソースイベント

Forward では、Session、Session Thread、Agent、Environment、Memory Store、Vault、Vault Credential のイベントも購読できます。完全なイベント名とトリガーはサポート対象の公開イベントを参照してください。 これらのイベントは同じ外側の envelope を使用しますが、data にはリソース参照のみが含まれ、Schedule イベントの schema_version、業務ステータス、結果本文は含まれません。たとえば、セッションがアイドル状態になった場合は次のようになります。
{
  "type": "event",
  "id": "whe_01k1jbxidled",
  "created_at": "2026-09-01T09:07:59Z",
  "data": {
    "type": "session.status_idled",
    "id": "sess_01k1jbsexample"
  }
}
イベントの分類data.id追加フィールド
session.status_*、session.updated、session.deletedSession IDなし。
session.thread_*所属する Session IDsession_thread_id:個々のスレッド ID。
agent.*Managed Agent IDなし。
environment.*Environment IDなし。
memory_store.*Memory Store IDなし。
vault.*Vault IDなし。
vault_credential.*Vault Credential IDvault_id:所属する Vault の ID。
表内の * は分類のためだけに使用しており、events に指定できる前方一致ワイルドカードではありません。通知を受け取ったら、リソース ID で最新の詳細を取得できます。削除イベントに対応するリソースは、すでに取得できない場合があります。session.status_idled は業務処理の成功を意味しないため、セッションの出力とステータスを併せて判断してください。 webhook.test は上記のリソースイベント構造を使用しません。テストペイロードはテストイベントを送信するを参照してください。

署名を検証する

Endpoint 作成時に返される signing_secret を使用してリクエスト署名を検証します。署名対象は次の 3 つの要素を連結したものです。
signed_content = Webhook-ID + "." + Webhook-Timestamp + "." + raw_body
解析や再シリアライズを行う前の生のリクエストボディを使用してください。 Python の例:
import base64
import hashlib
import hmac
import time


def verify_webhook(
    secret: str,
    webhook_id: str,
    timestamp: str,
    signature_header: str,
    raw_body: bytes,
    tolerance_seconds: int = 300,
) -> bool:
    if not secret.startswith("whsec_"):
        return False

    try:
        ts = int(timestamp)
        key = base64.b64decode(secret[len("whsec_"):], validate=True)
    except (ValueError, TypeError):
        return False

    if abs(int(time.time()) - ts) > tolerance_seconds:
        return False

    signed_content = (
        webhook_id.encode()
        + b"."
        + timestamp.encode()
        + b"."
        + raw_body
    )
    expected = base64.b64encode(
        hmac.new(key, signed_content, hashlib.sha256).digest()
    ).decode()

    signatures = [
        value.removeprefix("v1,")
        for value in signature_header.split()
        if value.startswith("v1,")
    ]
    return any(hmac.compare_digest(expected, value) for value in signatures)
次の点も検証してください。
  1. Webhook-Timestamp と現在時刻の差が 5 分以内であること。
  2. Webhook-Signature に検証成功する v1 署名が 1 つ以上含まれること。
  3. 処理済みの Webhook-ID で副作用のある業務処理を再実行しないこと。

リトライと冪等性

Webhook は少なくとも 1 回配信されます。ネットワーク障害、タイムアウト、または非 2xx レスポンス時には、同じイベントが再試行される場合があります。
  • Webhook-ID で重複を排除してください。到着時刻から冪等性キーを生成しないでください。
  • 同じ Webhook-ID の重複リクエストには成功を返し、副作用を繰り返さないでください。
  • 処理失敗時は非 2xx を返します。処理成功時、またはすでに処理済みの場合は 2xx を返します。

関連