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",
    "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,
    "push_status": "succeeded",
    "duration_ms": 15432
  }
}
字段类型说明
typestring固定为 event
idstringWebhook 事件 ID,与 Webhook-ID 相同。
created_atstring事件发生时间,RFC 3339 格式。
data.typestring事件类型,与 Webhook-Event-Type 相同。
data.idstring发生事件的资源 ID。
data.schema_versioninteger事件数据版本,当前为 1
客户端应忽略暂不识别的新增字段,以便兼容后续扩展。

Schedule 事件

forward.schedule.created

Schedule 创建成功时发送:
{
  "type": "event",
  "id": "whe_01k1jbxcreated",
  "created_at": "2026-09-01T09:00:00Z",
  "data": {
    "type": "forward.schedule.created",
    "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_typenext_trigger_at 仅在有对应值时返回。

forward.schedule_run.succeeded

Schedule Run 成功完成时发送。data.statuscompleted

forward.schedule_run.failed

Schedule Run 执行失败时发送。结构与成功事件相同,data.statusfailed,并可能包含 error_type
{
  "type": "event",
  "id": "whe_01k1jbxfailed",
  "created_at": "2026-09-01T09:07:59Z",
  "data": {
    "type": "forward.schedule_run.failed",
    "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,
    "push_status": "failed",
    "duration_ms": 1200,
    "error_type": "runtime_error"
  }
}
Schedule Run 事件的业务结果应以对应的 Schedule Run 查询接口为准;Webhook 用于通知状态变化。

签名校验

创建 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

相关