webhooks について
サーバーアプリケーションで Editframe の Webhook 通知を受信するためのガイドとコード例です。署名検証、イベントの永続的な受け入れ、重複処理に加え、レンダー、ファイル、パイプラインのイベント形式を説明します。
主な機能
- 01HTTPS Webhook URL とイベントトピックの設定
- 02生の本文に対する HMAC-SHA256 署名の検証
- 03応答前のイベント永続化
- 04重複配信と順不同配信への対応
- 05レンダー、ファイル、パイプラインのトピック説明
活用シーン
- 01Editframe のレンダーステータス更新の受信
- 02ファイルのライフサイクル通知の処理
- 03パイプラインの実行、ステップ、レビューの追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ editframe/ skills --skill webhooksインストール後、Claude Code で/webhooksを入力して読み込まれたか確認します。
詳細
Webhook
サーバーで、アクティブなクライアント接続に依存しない通知が必要な場合は、Webhook を使用します。
リアルタイムの進捗表示や単発のレンダリングには、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() などのミドルウェアより前に、このルーターをマウントしてください。そのミドルウェアが署名対象のバイト列を消費してしまいます。
永続的な受け入れ処理は短時間で完了させてください。Webhook の HTTP タイムアウトは 30 秒です。保存したイベントは、その後ワーカーで処理します。
すでに受け入れた重複イベントには成功を返してください。シークレットや署名対象のボディを漏らさず、ストレージの障害をアプリケーションログに記録してください。
すべてのリクエストには X-Webhook-Signature が含まれます。これは生の JSON ボディの HMAC-SHA256 を 16 進数でエンコードしたものです。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 を送信します。
パイプラインのトピック
Editframe は、各組織でパイプラインを有効にします。Editframe がパイプラインのトピックを送信するのは、パイプラインが有効な組織のみです。パイプラインを利用するには、映像素材について 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秒に1回までです。 | ステップ |
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 キーごとに各パイプライントピックを選択します。Editframe は、キーに登録されていないトピックを送信しません。
- その API キーで実行とグループを作成します。Editframe は、実行、ステップ、レビューのトピックを実行の API キーにのみ送信し、グループのトピックをグループを作成した API キーにのみ送信します。
- 実行の API キーは、その実行を作成したキーです。ダッシュボードでユーザーが開始する実行の場合は、Webhooks でユーザーが選択したキーです。ユーザーがキーを選択しない場合、実行は Webhook を送信しません。
- 1 つの実行の実行、ステップ、レビューイベントの順序付けには
sequenceを使用します。配信は順不同で届く場合があります。 - 同じ
attemptに対してpipeline_step.startedが再度届くことを想定してください。空いているワーカースロットを待つステップは、開始時に再度送信します。 - ステップが作成されたときや実行可能になったときにはイベントが届かず、レビュータスクが開いたときにもレビューイベントは届きません。
pipeline_step.waiting_reviewがタスクを通知します。 - すべての実行が完了したグループを閉じたときは、
pipeline_group.completedのみが届くことを想定してください。pipeline_group.reopenedの後には、再度pipeline_group.completedが届くことを想定してください。
パイプラインが有効な組織では、ダッシュボードの API キーフォームに、Pipeline events の下にパイプライントピックが表示されます。フォームを保存すると、フォームに表示されていないトピックはそのまま保持されます。ダッシュボードの実行ページには、実行とそのステップの Webhook イベントが、各配信試行とともに表示されます。
レガシートピック
Editframe は、非推奨となったファイルタイプ別 API に関連付けられた次のトピックを引き続き送信します。新しい連携では使用しないでください。
image_file.created、isobmff_file.created、isobmff_track.created、unprocessed_file.created。
配信と重複の処理
各イベントは HTTP POST として届きます。配信は重複することがあるため、順序や無制限の再試行を前提にしないでください。
data.id はリソースを識別するものであり、配信を識別するものではありません。複数のライフサイクルイベントが同じ ID を共有する場合があり、更新の繰り返しで同じトピックが使われる場合もあります。
本文のフィンガープリントを使用して、エンドポイントに設定された API キーのスコープ内で同一の配信を識別します。受け付けたイベントとともに、フィンガープリントをアトミックに保存してください。
本文が同一の場合、内容が同じ別々のイベントを区別できません。リソースの状態、またはアプリケーション側で管理する遷移キーを中心にビジネス処理を設計してください。
リソース ID、またはトピックと ID の組み合わせを、永続的な通知重複排除キーとして使用しないでください。
失敗した配信を監視し、重要なリソースの状態を API で照合してください。配信への確認応答は、ビジネス処理の成功を保証するものではありません。
テスト
ダッシュボードで API キーの Webhook URL を変更します。すると Editframe は、新しい URL にテストイベントを 1 件送信します。
{ topic: "webhook.test", data: { id: "<API key ID>", org_id: "<organization ID>" } }
このイベントには、キーの Webhook シークレットに由来する X-Webhook-Signature が含まれます。ハンドラーで webhook.test トピックを受け付けてください。
レンダリングまたはファイルのトピックをテストするには、実際にレンダリングするか、小さなファイルをアップロードしてください。
ローカル開発では、たとえば ngrok http 3000 を使って開発サーバーをトンネル経由で公開します。テスト中は、API キーの Webhook URL をトンネルの URL に設定してください。
フィードバックの送信
問題や改善点について、Editframe にフィードバックを送信してください。
- Editframe またはこのスキルが原因で問題が発生した場合は、回避策を見つけてタスクを続行してください。その後、問題と回避策を報告してください。
- 現在のタスクだけでなく、ほかのタスクにも役立つ改善点を見つけた場合は、提案を送信してください。たとえば、より明確な指示、不足している例、より適切なデフォルト、新機能などを提案できます。
問題や提案の内容が異なる場合は、それぞれ別のレポートを送信してください。レポートの送信に失敗した場合も、タスクを続行してください。
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 キーの指定は任意です。キーなしのレポートは匿名になります。レポートにシークレット、API キー、個人データを含めないでください。すべてのフィールド、制限、応答については、editframe-api スキルを参照してください。