> 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.

# Webhooks för Bild och video

> **Note**
>
> **Guide** · Förutsätter att du har slutfört [snabbstarten för Image & Video ](/docs/sv/eleven-api/guides/cookbooks/image-and-video).

## Översikt

Videogenereringar kan ta flera minuter, vilket gör polling dyrt att hålla öppet. Välj webhook-leverans
för en generering så skickar ElevenLabs en `flows_generation`-händelse till din slutpunkt
när genereringen når `completed` eller `failed`.

Händelsens payload är det slutliga svaret från motsvarande GET-slutpunkt, så en hanterare som
redan förstår polling-svaret behöver ingen separat tolkningsväg.

## Innan du börjar

Webhook-leverans använder de webhooks som din arbetsyta har prenumererat på för genereringshändelser. Att konfigurera en
kräver två steg: skapa webhooken och prenumerera sedan på händelsen.

#### Skapa en webhook

Gå till [**Utvecklare** > **Webhooks**](https://elevenlabs.io/app/developers/webhooks) och skapa en
webhook med en offentligt nåbar HTTPS-callback-URL. Spara signeringshemligheten du får tillbaka; du
behöver den för att verifiera inkommande händelser.

#### Prenumerera på genereringshändelser

Under **Välj händelser att lyssna på** markerar du **Image & Video API generation completed**. En webhook
som finns men inte prenumererar på denna händelse anropas aldrig.

Du kan göra samma sak via API:t genom att skicka händelsen `flows` till
[Uppdatera arbetsytans webhook](/docs/sv/api-reference/webhooks/update):

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

För att skapa och prenumerera på webhooks krävs behörigheten Webhooks Manage eller att du är arbetsyteadministratör. En
enskild händelse kan ha upp till 10 webhooks; därefter misslyckas begäran med `too_many_webhooks`.

En generering som begär webhook-leverans när ingen webhook prenumererar på genereringshändelser
avvisas, så ett resultat genereras aldrig utan någonstans att leverera det.

## Begär webhook-leverans

Lägg till ett `webhook`-objekt i skapa-begäran. Använd `{"type": "all"}` för att leverera till varje webhook
som prenumererar på genereringshändelser, vilket gör begäran stabil när webhooks läggs till eller ersätts.

```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" },
});
```

Om du i stället vill rikta in dig på specifika webhooks anger du fältet `webhook` som en lista med ID:n. Varje ID måste vara en
av arbetsytans webhooks som prenumererar på genereringshändelser.

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

Skapa-begäran validerar målet innan genereringen startar och returnerar ett fel när
leverans inte skulle vara möjlig:

| Felstatus                | Orsak                                                                                 |
| ------------------------ | ------------------------------------------------------------------------------------- |
| `no_webhooks_configured` | Leverans till alla webhooks begärdes, men arbetsytan har inga.                        |
| `invalid_webhook_id`     | En angiven webhook prenumererar inte på genereringshändelser eller finns inte längre. |
| `webhook_disabled`       | En riktad webhook är inaktiverad, manuellt eller automatiskt efter fel.               |

Webhook-leverans fungerar väl med [kedjade genereringar](/docs/sv/eleven-api/guides/how-to/image-and-video/references#chain-one-generation-into-the-next):
ange `webhook` för den sista genereringen så körs hela kedjan på serversidan med en enda händelse i
slutet. Detta gäller även om kedjan misslyckas halvvägs — felet sprider sig till den
sista genereringen, som levererar det som en `failed`-händelse med orsaken `dependency_failed`.

## Webhook-payload

En slutförd generering levererar utdata-URL:en och MIME-typen:

```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"
  }
}
```

En misslyckad generering levererar i stället felkategorin och meddelandet:

```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."
  }
}
```

Förgrena på `data.status` för att avgöra vilka fält som finns. De två slutliga statusarna är de enda
som en webhook kan innehålla, eftersom leverans endast sker när en generering är klar.

> **Warning**
>
> `content_url` är en signerad URL som upphör ungefär en timme efter att händelsen skickats. Ladda ned
> mediet direkt, eller hämta genereringen igen för en ny URL.

## Hantera händelsen

En hanterare verifierar signaturen, kontrollerar händelsetypen och förgrenar sedan på `data.status`. Detta
exempel laddar ned utdata från en slutförd generering och loggar orsaken för en misslyckad.

```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);
```

Båda exemplen laddar ned under begäran för korthetens skull. En stor video tar tillräckligt lång tid för att detta kan
överskrida leveranstimeouten, så i produktion bör du skicka genererings-ID:t till en kö och returnera 2xx
direkt. Den signerade URL:en är giltig i ungefär en timme, vilket räcker gott för en bakgrundsarbetare.

> **Tip**
>
> Om du vill ta emot händelser på en lokal server under utveckling kan du exponera den med en tunnel som
> [ngrok](https://ngrok.com/) och använda HTTPS-URL:en du får som webhookens callback-URL.

## Verifiera signaturen

Hanteraren ovan anropar `construct_event` / `constructEvent`, som verifierar huvudet
`ElevenLabs-Signature`, validerar tidsstämpeln och tolkar payloaden i ett steg. Verifiera alltid
innan du litar på en händelse.

Det är viktigt att mottagaren validerar alla inkommande webhooks. Webhooks har för närvarande stöd för autentisering via HMAC-signaturer. Konfigurera HMAC-autentisering genom att:

* Lagra den delade hemligheten som genereras när webhooken skapas på ett säkert sätt
* Verifiera headern ElevenLabs-Signature i din endpoint med hjälp av SDK:n

JavaScript-SDK:n exponerar `constructEvent`; Python-SDK:n exponerar `construct_event` med **`rawBody`**, **`sig_header`** och **`secret`** (dessa heter inte `payload` / `signature` i Python). Båda verifierar signaturen, validerar tidsstämpeln och tolkar JSON-payloaden.

#### Python

Exempel på webhook-hanterare med FastAPI:

```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

Exempel på webhook-hanterare med Express:

```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

Exempel på webhook-hanterare med Next.js API-route:

**`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 });
}
```

## Leveransbeteende

Varje generering levererar exakt en slutlig händelse per riktad webhook. Leverans är oberoende av
själva genereringen: en webhook som misslyckas eller inte kan nås påverkar inte resultatet, som
fortsätter vara tillgängligt från GET-slutpunkten och i listsvar.

Returnera en 2xx-status direkt från din hanterare. Upprepade fel inaktiverar automatiskt en webhook, och en
inaktiverad webhook gör att efterföljande genereringar som riktas mot den avvisas vid skapandet. Utforma
hanteraren så att den är idempotent och använd genereringens `id` för att ta bort dubbletter.

För arbetsflöden där ett missat resultat inte är acceptabelt bör du behandla webhooks som den snabba vägen och stämma av
regelbundet med `flows.image.list` eller `flows.video.list`, filtrerat på `status`.

## Nästa steg

#### [Referenser och tillgångar](/docs/sv/eleven-api/guides/how-to/image-and-video/references)

Vägled en generering med en tidigare generering, en uppladdad tillgång eller infogade medier.

#### [Webhook-konfiguration](/docs/sv/eleven-api/resources/webhooks)

Skapa, skydda och hantera webhooks för din arbetsyta.

#### [API-referens](/docs/sv/api-reference/flows/image/create)

Utforska slutpunkterna för bilder, video och tillgångar.