Webhook
Direct API の生成が完了したときに署名付きの通知を受け取り、署名を検証し、生成を読み取って詳細を確認します。
Webhook は、生成が最終ステータスに達したときに Morphic が呼び出す、お客様の HTTPS エンドポイントです。配信には署名が付き、約 1 日にわたって再試行され、ログに記録されます。各イベントは簡潔な通知で、どの生成がどのように完了したかだけを伝えます。アセット、課金、エラーメッセージは、お客様のキーで生成を読み取って確認します。
エンドポイントを登録する
POST /v1/direct/webhooks には generations:write スコープが必要です。
curl -s https://mcp.morphic.com/v1/direct/webhooks \
-H "Authorization: Bearer $MORPHIC_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://hooks.example.com/morphic",
"events": ["generation.succeeded", "generation.failed", "generation.cancelled"],
"description": "Production order pipeline"
}'| フィールド | ルール |
|---|---|
url | 公開ホスト上の HTTPS(IP リテラル、プライベートアドレス、ローカルアドレスは不可)、最大 2,048 文字 |
events | 1 つ以上の イベントタイプ |
description | 任意、最大 200 文字 |
レスポンスは 201 です。
{
"webhook": {
"id": "01929a10-0c3d-7f44-8a21-0b1c2d3e4f50",
"url": "https://hooks.example.com/morphic",
"events": ["generation.succeeded", "generation.failed", "generation.cancelled"],
"description": "Production order pipeline",
"created_at": "2026-10-25T08:00:00.000Z",
"signing_secret": "whsec_4b1e…"
}
}signing_secretはこのレスポンスでのみ表示されます。API キーと同様に保管してください。- シークレットをローテーションするには、新しいエンドポイントを登録し、呼び出しをその ID に切り替えてから、古いエンドポイントを削除します。
- 組織は最大 10 個のエンドポイントを保持できます。
エンドポイントを管理する
| リクエスト | スコープ | 内容 |
|---|---|---|
GET /v1/direct/webhooks | assets:read | エンドポイントを { "webhooks": [ … ] } として一覧表示します。シークレットは含みません |
DELETE /v1/direct/webhooks/{webhook_id} | generations:write | エンドポイントを削除します。そのエンドポイント宛てで保留中の配信は破棄されます |
呼び出しにエンドポイントを指定する
ツール呼び出しのボディに webhook_id を渡します。その呼び出しが開始したすべての生成は、エンドポイントが購読している最終ステータスに達したときに、そのエンドポイントに配信されます。webhook_id のない呼び出しでは Webhook は送信されません。
Morphic のサーバーで動画をレンダリングするツール(video_compose、video_* 系の変換、burn_subtitles)は、現時点では webhook_id を受け付けません。これらの生成は ポーリング してください。
イベントタイプ
| イベント | 送信されるタイミング |
|---|---|
generation.succeeded | 生成が出力を伴って完了したとき |
generation.failed | 生成が失敗したとき。請求額は返金されます |
generation.cancelled | 生成がキャンセルされたとき。請求額は返金されます |
- 各生成が生み出すイベントは最大 1 件です。
queuedとin_progressは配信されません。必要な場合はポーリングで確認してください。- 生成は開始から 72 時間監視されます。それより後に終了した生成はイベントを送信しないため、読み取って確認してください。
配信
POST /morphic HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: Morphic-Webhooks/1.0
Morphic-Event-Id: 0192a7c0-5d4e-7b3a-8c2d-1e0f9a8b7c6d
Morphic-Delivery-Id: 0192a7c0-6e5f-7c4b-9d3e-2f1a0b9c8d7e
Morphic-Event-Type: generation.succeeded
Morphic-Signature: t=1792919741,v1=5f8a0c…e21b
{
"id": "0192a7c0-5d4e-7b3a-8c2d-1e0f9a8b7c6d",
"type": "generation.succeeded",
"created_at": "2026-10-25T09:15:42.410Z",
"api_version": "v1",
"data": {
"generation": {
"object": "generation",
"id": "0192a7b3-a1c4-7e02-8f9d-1a2b3c4d5e6f",
"tool": "generate_video",
"status": "succeeded",
"completed_at": "2026-10-25T09:15:41.902Z",
"error_code": null,
"metadata": { "order_id": "4411" }
}
}
}data.generation には次のフィールドだけが含まれます。
| フィールド | 意味 |
|---|---|
object、id、tool、status、completed_at、metadata | 生成オブジェクト と同じ |
error_code | null、または failed の生成のコード |
イベントには出力、リンク、課金、モデル、エラーメッセージが一切含まれないため、配信が誤った宛先やログに届いても情報は漏れません。受信したら、お客様のキーで生成を読み取ってください。GET /v1/direct/generations/{id}、複数件の場合は 一括読み取り を使います。イベントが送信される時点で課金に関してそれ以上の変動はないため、その読み取り結果が最終です。
| ヘッダー | 意味 |
|---|---|
Morphic-Event-Id | イベントの ID。再試行のたびに同じ値です。これで重複を排除してください |
Morphic-Delivery-Id | この 1 回の試行。再試行ごとに異なります |
Morphic-Event-Type | ボディの type |
Morphic-Signature | t=<unix seconds>,v1=<hex HMAC-SHA256> |
イベントは通常、生成が最終ステータスに達してから約 1 分以内に送信されます。リアルタイムのストリームではありません。待っている 1 件の生成を最小の遅延で知るには、ロングポーリング を使ってください。
署名を検証する
v1 は、エンドポイントの signing_secret をキーとして、文字列 {t}.{raw request body} に対して計算した HMAC-SHA256 の 16 進表記です。
- ヘッダーを
,で分割し、tとv1を取り出します。 - JSON の解析や再シリアライズを行う前に、受信した生のバイト列に対して
HMAC_SHA256(secret, t + "." + raw_body)を計算します。 - 定数時間比較で
v1と比較します。 - リプレイを防ぐため、
tが現在時刻から 5 分以上離れている配信は拒否します。
import crypto from 'node:crypto';
import express from 'express';
const app = express();
app.post('/morphic', express.raw({ type: 'application/json' }), (req, res) => {
const header = req.get('Morphic-Signature') ?? '';
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const expected = crypto
.createHmac('sha256', process.env.MORPHIC_WEBHOOK_SECRET)
.update(`${parts.t}.${req.body}`)
.digest('hex');
const fresh = Math.abs(Date.now() / 1000 - Number(parts.t)) <= 300;
const valid =
parts.v1?.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(parts.v1), Buffer.from(expected));
if (!fresh || !valid) return res.sendStatus(400);
const event = JSON.parse(req.body);
enqueue(event); // process asynchronously, keyed by event.id
res.sendStatus(204);
});検証していないエンドポイントは開け放たれた扉と同じです。URL を知った人なら誰でも、生成が完了したと偽ってお客様に通知できます。すべての署名を検証し、5 分より古いタイムスタンプは拒否してください。
応答と再試行
- 10 秒以内に任意の
2xxで応答し、その後で処理を行ってください。 - それ以外はすべて失敗した試行とみなされます。
2xx以外のステータス、タイムアウト、接続の拒否、TLS エラーなどです。 - リダイレクトは追跡されません。
3xxは失敗した試行です。 - 順序は保証されず、同じイベントが複数回届くことがあります。ハンドラーは
Morphic-Event-Id、または生成ごとにイベントは最大 1 件なのでdata.generation.idに基づいて冪等にしてください。
失敗した試行は、次の間隔で再試行されます。
| 再試行 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
| 前回の試行からの待機時間 | 1 分 | 5 分 | 15 分 | 1 時間 | 3 時間 | 6 時間 | 12 時間 |
7 回目の再試行(最初の試行から約 22 時間後)の後、イベントは破棄されます。生成自体には影響はありません。GET /v1/direct/generations/{id} で読み取ってください。
配信ログ
GET /v1/direct/webhooks/{webhook_id}/events(スコープ assets:read)は、エンドポイントの直近 20 件のイベントを、送信されたボディそのものと各試行の結果とともに返します。1 件の生成のイベントだけを見るには ?generation_id=… を付けます。
{
"webhook_events": [
{
"id": "0192a7c0-5d4e-…",
"type": "generation.succeeded",
"generation_id": "0192a7b3-…",
"status": "delivered",
"created_at": "2026-10-25T09:15:42.410Z",
"payload": { "…": "the body that was sent" },
"attempts": [
{ "id": "0192a7c0-6e5f-…", "attempted_at": "2026-10-25T09:15:42.900Z", "response_status": 204, "duration_ms": 131, "error": null, "response_body_excerpt": null }
]
}
]
}イベントの status | 意味 |
|---|---|
pending | まだ配信されていません。今後も試行される可能性があります |
delivered | エンドポイントが 2xx で応答しました |
abandoned | 最後の再試行が失敗したか、エンドポイントが削除されました |
skipped | エンドポイントがそのイベントタイプを購読していませんでした |
Webhook はより安価で速い通知手段ですが、保証ではありません。通知が届いていない生成は、安全策としてときどきポーリングしてください。
何かお困りのことやご質問がありましたら、お気軽にご連絡ください。喜んでお手伝いします。こちらまでご連絡ください support@morphic.com.