Webhooks

実行をポーリングする代わりに、完了時に通知を受け取ります。

エンドポイントを一度登録し、run を開始するときにその webhook_id を渡すと、その run があなたの購読している状態に変わった時点でこちらから呼び出します。通常は1分以内です。

エンドポイントを登録する

POST {base_url}/webhooks

{
  "url": "https://hooks.example.com/morphic",
  "events": ["workflow_run.succeeded", "workflow_run.failed"],
  "description": "story pipeline"
}

runs:write が必要です。URL は認証情報を含まない公開の https URL である必要があります。組織ごとに保持できるエンドポイントは最大10件です。

201 レスポンスには、エンドポイントの id(run を開始するときに webhook_id として渡します)と、1回だけ表示される 署名シークレット(whsec_…)が含まれます。APIキーと同様に保存してください。

GET {base_url}/webhooks
DELETE {base_url}/webhooks/{webhook_id}

GET(runs:read)はエンドポイントの一覧を返し、シークレットは返しません。 DELETE(runs:write)は1件を削除します。そのエンドポイント宛てでまだ保留中の配信は破棄されます。シークレットをローテーションするには、新しいエンドポイントを登録して run をそちらへ移してください。

イベント

イベント発火するタイミング
workflow_run.succeededrun が完了し、アセットの準備が整ったとき
workflow_run.failedrun が停止したとき。error が理由を示します
workflow_run.needs_inputrun が Morphic Studio で人の入力待ちのとき
workflow_run.expiredrun が有効期間内に完了しなかったとき

配信

POST https://hooks.example.com/morphic
Morphic-Event-Id: 0192f4a0-3c71-7b92-8e14-5d6f2a9b3c07
Morphic-Delivery-Id: 0192f4a0-4d82-7c03-9f25-6e7a3b0c4d18
Morphic-Event-Type: workflow_run.succeeded
Morphic-Signature: t=1790056632,v1=3f9c1a…

{
  "id": "0192f4a0-3c71-7b92-8e14-5d6f2a9b3c07",
  "type": "workflow_run.succeeded",
  "created_at": "2026-09-22T04:17:12Z",
  "api_version": "v1",
  "data": {
    "run": { "id": "0192f3d0-…", "status": "succeeded", "assets": [], "credits_used": 1792 }
  }
}

run オブジェクトは ワークフロー run を読み取る が返すものと同じ形で、assets も含まれます — そのため、成功した配信には追加の呼び出しは不要です。これはイベント発生時点の run の状態であり、配信の再試行が続く限り、アセットリンクは約1日有効です。

すべての配信を検証する

署名シークレットで {timestamp}.{raw body} の HMAC-SHA256 を計算し、v1 と定数時間で比較してください。JSON をパースする前の 生の body を使ってください。

import crypto from 'node:crypto';

function verify({ rawBody, header, secret }) {
  const parts = Object.fromEntries(
    header.split(',').map((pair) => pair.split('=')),
  );

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${parts.t}.${rawBody}`)
    .digest('hex');

  const signed = Buffer.from(expected);
  const given = Buffer.from(parts.v1);

  if (signed.length !== given.length) return false;
  if (!crypto.timingSafeEqual(signed, given)) return false;

  // Refuse a replayed delivery.
  return Math.abs(Date.now() / 1000 - Number(parts.t)) < 300;
}

未検証の webhook エンドポイントは開いた扉のようなものです。URLを知った誰でも、run が成功したと通知できます。署名を検証し、5分より古い timestamp は拒否してください。

再試行と順序

  • 10秒 以内に 2xx で応答してください。まず応答を返し、その後で処理してください — ハンドラが遅いと失敗とみなされます。
  • 2xx 以外の応答またはタイムアウトは約1日指数バックオフで再試行され、その後破棄されます。リダイレクトは追従されず、失敗として扱われます。
  • エンドポイントが購読しているイベントだけが送信されます。別の状態に達した run については何も通知されません。
  • 配信は 順不同かつ複数回届くことがあります。Morphic-Event-Id をキーにし、すでに処理したものは無視してください。
  • succeeded の配信は、run の自分側の記録が存在する前に届くことがあります。エラーにせずそれを処理してください — あるいは、その run についてはポーリングにフォールバックしてください。

webhook は最適化であり、保証ではありません。まだ受け取っていない run をポーリングする遅めのスイープを維持してください。そうすれば、あなた側の1回の不具合デプロイで1日の出力を失うことはありません。

このページの内容