> This is a page from the ElevenLabs documentation. For a complete page index, fetch https://elevenlabs.io/docs/llms.txt. For the full documentation in a single file, fetch https://elevenlabs.io/docs/llms-full.txt.

# 画像＆ビデオのWebhook

> **Note**
>
> **ハウツーガイド** · [画像＆ビデオの クイックスタート](/docs/ja/eleven-api/guides/cookbooks/image-and-video)を完了していることを前提としています。

## 概要

ビデオ生成には数分かかる場合があり、その間ポーリング接続を維持するとコストがかかります。生成でWebhook配信を有効にすると、生成が`completed`または`failed`に達した時点で、ElevenLabsからエンドポイントに`flows_generation`イベントが送信されます。

イベントペイロードは対応するGETエンドポイントの最終レスポンスです。そのため、すでにポーリングレスポンスを処理できるハンドラーなら、別の解析処理は不要です。

## 始める前に

Webhook配信では、ワークスペースで生成イベントに登録済みのWebhookを使用します。設定は2ステップです。まずWebhookを作成し、次にイベントを登録します。

#### Webhookを作成

[**Developers** > **Webhooks**](https://elevenlabs.io/app/developers/webhooks)に移動し、外部からアクセス可能なHTTPSコールバックURLを持つWebhookを作成します。返される署名シークレットは保管してください。受信イベントの検証に必要です。

#### 生成イベントに登録

**Select events to listen to**で、**Image & Video API generation or template run
completed**にチェックを入れます。同じイベントで、完了した[テンプレート実行](/docs/ja/eleven-api/guides/how-to/templates/run-a-template)も配信されます。
Webhookを作成しても、このイベントに登録していなければ呼び出されません。

APIからも、[ワークスペースWebhookを更新](/docs/ja/api-reference/webhooks/update)する際に`flows`イベントを渡すことで同様に設定できます。

```json
{
  "events": ["flows"]
}
```

Webhookの作成と登録には、Webhooks Manage権限またはワークスペース管理者権限が必要です。1つのイベントには最大10個のWebhookを登録できます。超えるとリクエストは`too_many_webhooks`で失敗します。

生成イベントに登録されたWebhookがない状態でWebhook配信をリクエストした生成は拒否されます。そのため、配信先がないまま結果が生成されることはありません。

## Webhook配信をリクエスト

作成リクエストに`webhook`オブジェクトを追加します。`{"type": "all"}`を使用すると、生成イベントに登録されたすべてのWebhookに配信されます。これにより、Webhookが追加または置き換えられてもリクエストを変更せずに済みます。

```python
from elevenlabs import VideoGenerationRequest_Veo31FastGenerate001, WebhookTarget_All

generation = elevenlabs.flows.video.create(
    request=VideoGenerationRequest_Veo31FastGenerate001(
        prompt="A corgi rides a tiny surfboard across a sunlit wave at golden hour, cinematic",
        duration_secs=8,
        webhook=WebhookTarget_All(),
    )
)
```

```typescript
const generation = await elevenlabs.flows.video.create({
  modelId: "veo-3.1-fast-generate-001",
  prompt: "A corgi rides a tiny surfboard across a sunlit wave at golden hour, cinematic",
  durationSecs: 8,
  webhook: { type: "all" },
});
```

特定のWebhookを対象にするには、代わりに`webhook`フィールドへIDのリストを設定します。各IDは、生成イベントに登録されたワークスペースのWebhookである必要があります。

```json
{
  "webhook": {
    "type": "ids",
    "ids": ["Q8mVr2LpXcT4nB6yJdKw"]
  }
}
```

作成リクエストは生成開始前に対象を検証し、配信できない場合はエラーを返します。

| エラーステータス                 | 原因                                                 |
| ------------------------ | -------------------------------------------------- |
| `no_webhooks_configured` | すべてのWebhookへの配信がリクエストされましたが、ワークスペースにWebhookがありません。 |
| `invalid_webhook_id`     | 指定されたWebhookが生成イベントに登録されていないか、すでに存在しません。           |
| `webhook_disabled`       | 対象のWebhookが、手動または障害後の自動処理によって無効化されています。            |

Webhook配信は[連鎖生成](/docs/ja/eleven-api/guides/how-to/image-and-video/references#chain-one-generation-into-the-next)と相性が良いです。最後の生成に`webhook`を設定すると、チェーン全体がサーバー側で実行され、最後に1つのイベントが送信されます。チェーンの途中で失敗した場合も同様です。失敗は最終生成まで伝播し、`dependency_failed`理由の`failed`イベントとして配信されます。

## Webhookペイロード

完了した生成では、出力URLとMIMEタイプが配信されます。

```json
{
  "type": "flows_generation",
  "event_timestamp": 1739721600,
  "data": {
    "id": "JWr5N6X9ZTqf8jD2LmQb",
    "status": "completed",
    "content_url": "https://storage.googleapis.com/generations/JWr5N6X9ZTqf8jD2LmQb",
    "content_mime_type": "video/mp4"
  }
}
```

失敗した生成では、代わりに失敗カテゴリとメッセージが配信されます。

```json
{
  "type": "flows_generation",
  "event_timestamp": 1739721600,
  "data": {
    "id": "JWr5N6X9ZTqf8jD2LmQb",
    "status": "failed",
    "failure_reason": "timeout",
    "error_message": "Timed out while processing. You were not charged for this generation."
  }
}
```

`data.status`で分岐して、どのフィールドが存在するかを判断します。Webhookで配信されるのはこの2つの最終ステータスのみです。配信は生成完了時にのみ行われます。

> **Warning**
>
> `content_url`は、イベント送信後およそ1時間で期限切れになる署名付きURLです。すぐにメディアをダウンロードするか、新しいURLを取得するために生成を再取得してください。

## イベントを処理

ハンドラーでは署名を検証し、イベントタイプを確認してから、`data.status`で分岐します。この例では、完了した生成の出力をダウンロードし、失敗した生成の理由をログに記録します。

```python maxLines=0
# server.py
import os

import requests
from dotenv import load_dotenv
from elevenlabs.client import ElevenLabs
from elevenlabs.errors import BadRequestError
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse

load_dotenv()

app = FastAPI()
elevenlabs = ElevenLabs(api_key=os.getenv("ELEVENLABS_API_KEY"))
WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")


@app.post("/webhook/flows")
async def receive_generation(request: Request):
    payload = await request.body()
    signature = request.headers.get("elevenlabs-signature")

    try:
        event = elevenlabs.webhooks.construct_event(
            rawBody=payload.decode("utf-8"),
            sig_header=signature,
            secret=WEBHOOK_SECRET,
        )
    except BadRequestError:
        return JSONResponse(content={"error": "Invalid signature"}, status_code=401)

    # construct_event returns a parsed dict, not an object with attributes.
    if event.get("type") != "flows_generation":
        return {"status": "ignored"}

    generation = event["data"]
    if generation["status"] == "completed":
        media = requests.get(generation["content_url"]).content
        with open(f"{generation['id']}.mp4", "wb") as f:
            f.write(media)
    else:
        print(f"Generation {generation['id']} failed: {generation['failure_reason']}")

    return {"status": "received"}
```

```typescript maxLines=0
// server.mts
import { ElevenLabsClient } from "@elevenlabs/elevenlabs-js";
import "dotenv/config";
import express from "express";
import { writeFile } from "fs/promises";

const elevenlabs = new ElevenLabsClient();
const app = express();

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// The raw body is required: verification runs over the exact bytes sent.
app.post("/webhook/flows", express.raw({ type: "application/json" }), async (req, res) => {
  const signature = req.headers["elevenlabs-signature"] as string;

  let event;
  try {
    event = await elevenlabs.webhooks.constructEvent(
      req.body.toString(),
      signature,
      WEBHOOK_SECRET
    );
  } catch {
    return res.status(401).json({ error: "Invalid signature" });
  }

  if (event.type !== "flows_generation") {
    return res.status(200).json({ received: true });
  }

  const generation = event.data;
  if (generation.status === "completed") {
    const response = await fetch(generation.content_url);
    await writeFile(`${generation.id}.mp4`, Buffer.from(await response.arrayBuffer()));
  } else {
    console.error(`Generation ${generation.id} failed: ${generation.failure_reason}`);
  }

  res.status(200).json({ received: true });
});

app.listen(3000);
```

簡潔にするため、どちらの例もリクエスト内でダウンロードしています。大きなビデオでは配信タイムアウトを超えるほど時間がかかる可能性があるため、本番環境では生成IDをキューに渡し、すぐに2xxを返してください。署名付きURLは約1時間有効で、バックグラウンドワーカーには十分です。

> **Tip**
>
> 開発中にローカルサーバーでイベントを受信するには、[ngrok](https://ngrok.com/)などのトンネルでサーバーを公開し、発行されたHTTPS URLをWebhookのコールバックURLとして使用します。

## 署名を検証

上記のハンドラーは`construct_event`／`constructEvent`を呼び出します。これにより、`ElevenLabs-Signature`ヘッダーの検証、タイムスタンプの検証、ペイロードの解析を一度に行います。イベントを信頼する前に、必ず検証してください。

受信側は、すべての受信Webhookを検証することが重要です。Webhookは現在、HMAC署名による認証に対応しています。HMAC認証は次の手順で設定します。

* Webhookの作成時に生成された共有シークレットを安全に保存する
* SDKを使用してエンドポイントでElevenLabs-Signatureヘッダーを検証する

JavaScript SDKでは`constructEvent`を、Python SDKでは\*\*`rawBody`**、**`sig_header`**、**`secret`\*\*を指定する`construct_event`を利用できます（Pythonでは`payload`／`signature`という名前ではありません）。どちらも署名の検証、タイムスタンプの検証、JSONペイロードの解析を行います。

#### Python

FastAPIを使用したWebhookハンドラーの例：

```python
from dotenv import load_dotenv
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from elevenlabs.client import ElevenLabs
from elevenlabs.errors import BadRequestError
import os

load_dotenv()

app = FastAPI()
elevenlabs = ElevenLabs(
    api_key=os.getenv("ELEVENLABS_API_KEY"),
)

WEBHOOK_SECRET = os.getenv("WEBHOOK_SECRET")

@app.post("/webhook")
async def receive_message(request: Request):
    payload = await request.body()
    signature = request.headers.get("elevenlabs-signature")

    try:
        event = elevenlabs.webhooks.construct_event(
            rawBody=payload.decode("utf-8"),
            sig_header=signature,
            secret=WEBHOOK_SECRET,
        )
    except BadRequestError as e:
        return JSONResponse(content={"error": "Invalid signature"}, status_code=401)

    # construct_event returns a dict (parsed JSON), not an object with attributes
    if event.get("type") == "post_call_transcription":
        print(f"Received transcription: {event.get('data')}")

    return {"status": "received"}
```

#### JavaScript

#### Express

Expressを使用したWebhookハンドラーの例：

```javascript
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';
import express from 'express';

const app = express();

const elevenlabs = new ElevenLabsClient();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// Use express.text() to preserve raw body for signature verification
app.post('/webhook', express.text({ type: 'application/json' }), async (req, res) => {
  const signature = req.headers['elevenlabs-signature'];
  const payload = req.body; // Raw string body

  let event;
  try {
    event = await elevenlabs.webhooks.constructEvent(payload, signature, WEBHOOK_SECRET);
  } catch (error) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  // Process the webhook event
  if (event.type === 'post_call_transcription') {
    console.log('Received transcription:', event.data);
  }

  res.status(200).json({ received: true });
});
```

#### Next.js

Next.js APIルートを使用したWebhookハンドラーの例：

**`app/api/webhook/route.ts`**

```typescript app/api/webhook/route.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
import { ElevenLabsClient } from '@elevenlabs/elevenlabs-js';

const elevenlabs = new ElevenLabsClient();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get('elevenlabs-signature');

  let event;
  try {
    event = await elevenlabs.webhooks.constructEvent(body, signature, WEBHOOK_SECRET);
  } catch (error) {
    return NextResponse.json({ error: 'Invalid signature' }, { status: 401 });
  }

  // Process the webhook event
  if (event.type === 'post_call_transcription') {
    console.log('Received transcription:', event.data);
  }

  return NextResponse.json({ received: true }, { status: 200 });
}
```

## 配信動作

各生成では、対象のWebhookごとに最終イベントが1件だけ配信されます。配信は生成自体とは独立しています。Webhookが失敗または到達不能でも結果には影響せず、GETエンドポイントおよびリストレスポンスから引き続き利用できます。

ハンドラーから速やかに2xxステータスを返してください。失敗が繰り返されるとWebhookは自動的に無効化され、無効なWebhookを対象とする以降の生成は作成時に拒否されます。ハンドラーは冪等になるよう設計し、生成の`id`で重複を排除してください。

結果の取りこぼしが許容されないワークフローでは、Webhookを高速経路として扱い、定期的に`flows.image.list`または`flows.video.list`を`status`でフィルタリングして照合してください。

## 次のステップ

#### [参照とアセット](/docs/ja/eleven-api/guides/how-to/image-and-video/references)

前の生成、アップロード済みアセット、またはインラインメディアを使って生成を指定します。

#### [Webhook設定](/docs/ja/eleven-api/resources/webhooks)

ワークスペースのWebhookを作成、保護、管理します。

#### [APIリファレンス](/docs/ja/api-reference/flows/image/create)

画像、ビデオ、アセットのエンドポイントを確認します。