关于 webhooks
为服务器应用接收 Editframe webhook 通知提供指南和代码模式,涵盖签名验证、持久化接收、重复事件处理,以及渲染、文件和流水线事件负载。
主要功能
- 01配置 HTTPS webhook URL 和事件主题
- 02验证原始请求体的 HMAC-SHA256 签名
- 03响应前持久化接收事件
- 04处理重复及乱序投递
- 05说明渲染、文件和流水线主题
适用场景
- 01接收 Editframe 渲染状态更新
- 02处理文件生命周期通知
- 03跟踪流水线运行、步骤和审核
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ editframe/ skills --skill webhooks安装后在 Claude Code 中输入/webhooks确认已加载。
详细介绍
Webhooks
当服务器需要在客户端连接非活跃时接收通知,请使用 webhooks。
对于实时进度显示或一次性渲染,请使用 getRenderProgress 或 getFileProcessingProgress。这些函数会使用事件流。
SDK 中没有用于注册 webhook 的函数。请通过控制面板(API → API Keys,或 editframe.com/resource/api_keys)为 API 密钥配置 webhook。设置 Webhook URL(必须使用 HTTPS),并选择要接收的 Webhook Events(主题)。创建或更新密钥时,控制面板会生成 Webhook Secret,用于对投递进行签名。复制此密钥,并与 API 密钥一同保存。
处理 Webhook
在接受请求前验证签名并校验载荷。返回成功前先持久化事件。
示例接受一个由应用程序定义的 acceptEvent 函数。该函数必须先将事件提交到持久化收件箱或队列,然后其 Promise 才能完成。
import express from "express";
import crypto from "node:crypto";
type WebhookEvent = { topic: string; data: Record<string, unknown> };
type AcceptEvent = (event: WebhookEvent, fingerprint: string) => Promise<void>;
export function createWebhookRouter(secret: string, acceptEvent: AcceptEvent) {
if (!secret) throw new Error("Webhook secret is required");
const router = express.Router();
router.post("/webhooks/editframe", express.raw({ type: "*/*" }), async (req, res) => {
const signature = req.get("x-webhook-signature");
const rawBody = req.body;
if (!signature || !/^[a-f0-9]{64}$/i.test(signature)) {
return res.status(401).send("Invalid signature");
}
if (!Buffer.isBuffer(rawBody)) {
return res.status(400).send("Raw body required");
}
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest();
if (!crypto.timingSafeEqual(Buffer.from(signature, "hex"), expected)) {
return res.status(401).send("Invalid signature");
}
let payload: WebhookEvent;
try {
const value = JSON.parse(rawBody.toString("utf-8"));
if (!value || typeof value.topic !== "string" || !value.data ||
typeof value.data !== "object" || Array.isArray(value.data)) {
return res.status(400).send("Invalid payload");
}
payload = value;
} catch {
return res.status(400).send("Invalid JSON");
}
const fingerprint = crypto.createHash("sha256").update(rawBody).digest("hex");
try {
await acceptEvent(payload, fingerprint);
return res.status(200).send("OK");
} catch {
return res.status(500).send("Event not accepted");
}
});
return router;
}
将此路由器挂载在解析请求正文的中间件(例如 express.json())之前。此类中间件会消耗已签名的字节。
保持持久化接收过程简短;投递 HTTP 超时时间为 30 秒。之后由工作进程处理已存储的事件。
对于已接受的重复事件,返回成功。将存储故障记录到应用程序日志中,但不要暴露密钥或已签名的正文。
每个请求都包含 X-Webhook-Signature:原始 JSON 正文的十六进制编码 HMAC-SHA256。重新序列化 JSON 可能会生成不同的签名。
载荷
{ topic: string, data: {...} }
渲染主题:render.created、render.pending、render.rendering、render.completed、render.failed
data 包含 id、status、created_at、completed_at、failed_at、width、height、fps、byte_size、duration_ms、md5、metadata、expires_at(null = 永久)、download_url(完成后填充)、error(失败时填充)。
文件主题:file.created、file.uploading、file.processing、file.ready、file.failed、file.updated
data 包含 id、type(video/image/caption)、status、filename、byte_size、md5、mime_type、width、height、expires_at。对于不属于其他文件主题的文件状态更改,Editframe 会发送 file.updated。
Pipeline 主题
Editframe 会为每个组织启用 pipelines。Editframe 只向已启用 pipelines 的组织发送 pipeline 主题。如需获取 pipelines,请向 Editframe 介绍您的素材:https://editframe.com/factory#contact。
| 主题 | Editframe 发送时机 | data |
|---|---|---|
pipeline_run.created | 创建运行时(pending)。 | 运行 |
pipeline_run.started | 运行开始执行其步骤时(running)。 | 运行 |
pipeline_run.retried | 重试状态为 failed 或 cancelled 的运行时。 | 运行 |
pipeline_run.completed | 运行成功时。 | 运行 |
pipeline_run.failed、pipeline_run.filtered、pipeline_run.cancelled | 运行以相应状态结束时。 | 运行 |
pipeline_step.started | 步骤开始时。 | 步骤 |
pipeline_step.progress | 分段步骤的 watermark_ms 增加时。每个步骤每 10 秒最多发送一个事件。 | 步骤 |
pipeline_step.waiting_external | 端点返回 202,或 redact_render 步骤开始渲染时。该步骤会等待回调或渲染完成。 | 步骤 |
pipeline_step.waiting_review | 审核步骤创建审核任务时。 | 步骤 |
pipeline_step.retrying | 可重试的失败后,或重试运行后,步骤即将开始新一轮尝试时。 | 步骤 |
pipeline_step.completed、pipeline_step.failed、pipeline_step.skipped、pipeline_step.cancelled | 步骤成功、失败、跳过或取消时。 | 步骤 |
pipeline_review.decided | 用户或 API 密钥批准或拒绝审核时。 | 审核 |
pipeline_review.expired | 审核在没有决策的情况下过期时。 | 审核 |
pipeline_group.created | 创建状态为 open 或 closed 的组时。 | 组 |
pipeline_group.closed | 一个打开的组被关闭,且其中部分运行尚未完成时。 | 组 |
pipeline_group.completed | 已关闭组中的所有运行均完成时。 | 组 |
pipeline_group.reopened | 重试某个运行后,已完成的组再次变为 closed 时。 | 组 |
file.receiving | 流式上传开始时。 | 文件 |
file.sealed | 流式上传封存时。对于流式上传,此主题取代 file.processing。 | 文件 |
- 运行
data:id、pipeline_id、pipeline_version、pipeline_digest、group_id、file_id、external_id、metadata、status、sequence、created_at、completed_at、error。 - 步骤
data:id、pipeline_run_id、run_metadata、step_key、namespace、type、status、attempt、sequence、started_at、completed_at、watermark_ms、review_task_id、error。 - 审核
data:id、pipeline_run_id、run_metadata、step_run_id、step_key、attempt、status(approved、rejected或expired)、decision({ decision, decided_by: { type, id }, reason },或过期后的null)、sequence、created_at、expires_at、decided_at。事件不包含批准时的更正内容。使用getPipelineReview获取审核内容。 - 组
data:id、pipeline_id、pipeline_version、external_id、metadata、status、completion_count、runs(total、succeeded、failed、filtered、cancelled)、created_at、closed_at、completed_at。 metadata和run_metadata包含创建运行或组时的metadata。- 只有在筛选器或审核停止所有分支时,运行才会以
filtered状态结束。若某个分支停止,但最终步骤成功,则会发送pipeline_run.completed。每个跳过的步骤都会发送pipeline_step.skipped。 - 文件
data的字段与其他文件主题相同。 error为null或{ message, code }。
请遵守以下规则:
- 在 API key 上选择每个流水线主题。Editframe 不会发送该 key 未列出的主题。
- 使用该 API key 创建运行和分组。Editframe 只会将运行、步骤和审核主题发送到创建该运行的 API key,并且只会将分组主题发送到创建该分组的 API key。
- 运行使用创建它的 API key。对于由用户在仪表板中启动的运行,该 key 是用户在 Webhooks 下选择的 key。如果用户未选择任何 key,运行不会发送任何 webhook。
- 使用
sequence对同一运行的运行、步骤和审核事件排序。投递可能会乱序到达。 - 预期同一
attempt可能再次收到pipeline_step.started。等待空闲工作线程槽位的步骤开始时会再次发送该事件。 - 步骤创建或变为就绪时不会发送事件,审核任务打开时也不会发送审核事件。
pipeline_step.waiting_review会通知审核任务。 - 关闭一个所有运行都已完成的分组时,只会收到
pipeline_group.completed。收到pipeline_group.reopened后,预期会再次收到pipeline_group.completed。
对于已启用流水线的组织,仪表板中的 API key 表单会在 Pipeline events 下列出流水线主题。保存表单时,会保留表单未列出的主题。仪表板中的运行页面会列出该运行及其步骤的 webhook 事件和每次投递尝试。
旧版主题
Editframe 仍会发送这些与已弃用的按类型文件 API 相关的主题。请勿基于这些主题构建新的集成。
image_file.created、isobmff_file.created、isobmff_track.created、unprocessed_file.created。
投递与重复处理
每个事件都会作为一次 HTTP POST 到达。投递可能重复;请勿假定事件有序,也不要假定重试次数没有上限。
data.id 用于标识资源,而非投递。多个生命周期事件可以共享该 ID,重复更新也可能共享同一主题。
使用请求体指纹,在端点配置的 API key 范围内识别相同的投递。将其与已接受的事件原子地存储。
相同的请求体无法区分内容完全相同的独立事件。请围绕资源状态或应用拥有的转换键来设计业务操作。
请勿将资源 ID 或主题与 ID 的组合用作永久通知去重键。
监控失败的投递,并通过 API 对重要资源状态进行对账。收到投递确认并不代表业务处理成功。
测试
在仪表板中更改 API key 的 Webhook URL。随后 Editframe 会向新 URL 发送一个测试事件:
{ topic: "webhook.test", data: { id: "<API key ID>", org_id: "<organization ID>" } }
该事件携带 X-Webhook-Signature,其值来自该 key 的 webhook secret。在处理程序中接受 webhook.test 主题。
若要测试渲染或文件主题,请创建真实渲染或上传一个小文件。
本地开发时,可以通过隧道公开开发服务器,例如使用 ngrok http 3000。测试期间,将 API key 的 Webhook URL 指向隧道 URL。
发送反馈
向 Editframe 反馈问题和改进建议:
- 如果 Editframe 或此 skill 导致问题,请找到解决方法并继续任务。之后报告问题和解决方法。
- 如果你发现也能帮助其他任务的改进,而不仅限于当前任务,请发送建议。例如,建议改进说明、补充示例、更合适的默认设置或新功能。
每个不同的问题或建议分别提交一份报告。如果报告提交失败,请继续任务。
curl -sS https://editframe.com/api/v1/feedback \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $EDITFRAME_API_KEY" \
--data-binary @- <<'JSON'
{
"category": "bug",
"skill": "webhooks",
"summary": "One line that names the problem or the suggestion",
"details": "For a problem: steps, expected result, actual result, and errors. For a suggestion: the change and how it helps.",
"workaround": "The workaround that you used. Omit this field for a suggestion."
}
JSON
将 category 设置为 bug、documentation、feature_request、performance 或 other。API key 为可选项。不提供 key 的报告将以匿名方式提交。请勿在报告中填写密钥、API key 或个人数据。所有字段、限制和响应详见 editframe-api skill。