Skip to main content
Webhooks

接收 Webhook 事件

验证 Webhook 请求来源,并可靠处理可能重复投递的事件。

Webhook 当前为 Beta 功能,接口、字段和行为可能在后续版本中调整。

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 业务事件使用统一信封:
{
  "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 事件

以下四个 Schedule 事件的 data 均包含 source 和 source_session_id,语义见 Schedule 创建来源。Run 事件继承父 Schedule 的创建来源。 服务部署后新入队的事件始终返回这两个字段;历史事件及其重试可能缺失,客户端应兼容字段缺失。需要时可通过获取 Schedule或获取 Schedule Run查询来源。

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.archived

Schedule 首次从未归档状态变为已归档时发送,data.status 固定为 archived。 覆盖以下归档途径:
  • 调用归档 API,或在会话中调用 delete_forward_schedule 工具。
  • 批量归档,每个本次实际归档的 Schedule 分别发送一条事件。
  • 一次性任务运行结束,以及触发计划因 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 和创建会话 ID。
status固定为 archived。查询 Schedule 对象时,仍应根据非空 archived_at 判断归档状态。
archived_at实际归档时间,RFC 3339 格式;外层 created_at 对应该时间。
归档状态和通知事件在同一事务中保存。重复归档不会生成新的事件,但同一事件仍可能因投递重试多次到达,请使用 Webhook-ID 去重。已归档的历史数据不会补发通知。 该事件表示 Schedule 已归档,不表示已有 Run 已结束,也不表示清空 Identity 的其他清理步骤全部完成。归档通知与创建、Run 结果通知不保证到达顺序;同步本地状态时,不要用迟到的创建通知把已归档任务恢复为生效状态。 这是独立的 Webhook 通知,不会新增 Session 中的归档事件。已有 Endpoint 需将 forward.schedule.archived 加入 events;订阅 * 的 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 事件的业务结果应以对应的 Schedule Run 查询接口为准;Webhook 用于通知状态变化。 需要输入全文时,使用回调中的 data.run_id 调用获取 Schedule Run,并传 include_initial_events=true。响应只在本次尝试的输入已绑定且可确认时附带 initial_events;不要用当前 Schedule 的配置代替该次输入。
curl -s 'https://api.qoder.com.cn/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
delete_forward_scheduleforward.schedule.archivedforward.schedule.archive_failed
创建、归档成功沿用原有资源事件,不额外发送第二条成功通知。查询没有匹配结果仍为成功;归档没有匹配结果为失败,匹配多个对象时返回 schedule_match_ambiguous,要求选择具体 Schedule ID,不会归档候选对象。

查询成功示例

{
  "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。当前调用所在会话见 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 路径的创建、归档成功仍沿用原有资源事件结构。资源事件的 source 和 source_session_id 继续表示 Schedule 的创建来源,tool_call 才表示本次管理调用。 这些结果通知已默认启用,Endpoint 仍需订阅相应的完整事件名;订阅 * 会接收新增事件,不支持 forward.schedule.* 等前缀通配符。可信调用来源无法确认、基础设施故障或提交结果不确定时,不保证产生业务失败通知。 同一已保存事件的投递重试沿用 Webhook-ID,接收端应据此去重。stcall_* 用于关联通知,不是上游工具调用的幂等键;独立 MCP 请求会独立执行,不能因结果不确定而自动重试创建或归档操作。不要依赖管理通知和 Run 通知的到达顺序。

Managed Agents 资源事件

Forward 也支持订阅 Session、Session Thread、Agent、Environment、Memory Store、Vault 和 Vault Credential 事件。完整事件名及触发时机见支持的公开事件。 这些事件使用相同的外层信封,但 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 用于验证请求签名。签名内容由以下三部分组成:
signed_content = Webhook-ID + "." + Webhook-Timestamp + "." + raw_body
校验时需要使用未经解析、未经重新序列化的原始请求 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 签名。
  3. 已处理过的 Webhook-ID 不再重复执行有副作用的业务操作。

重试与幂等

Webhook 使用至少一次投递语义。网络失败、超时或接收端返回非 2xx 时,系统可能重试同一事件。
  • 使用 Webhook-ID 去重,不要使用请求到达时间生成幂等键。
  • 同一个 Webhook-ID 的重复请求应返回成功,且不得重复执行副作用。
  • 处理失败时返回非 2xx;处理成功或已处理过时返回 2xx。

相关