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

# Webhooki po rozmowie

## Przegląd

Webhooki [po rozmowie](/docs/pl/eleven-api/resources/webhooks) pozwalają otrzymać szczegółowe informacje o rozmowie po zakończeniu analizy. Po włączeniu ElevenLabs wyśle żądanie POST do wskazanego endpointu z pełnymi danymi rozmowy.

ElevenLabs obsługuje trzy typy webhooków po rozmowie:

* **Webhooki transkrypcji** (`post_call_transcription`): zawierają pełne dane rozmowy, w tym transkrypcje, wyniki analizy i metadane
* **Webhooki audio** (`post_call_audio`): zawierają minimalne dane z dźwiękiem całej rozmowy zakodowanym w base64
* **Webhooki nieudanego rozpoczęcia rozmowy** (`call_initiation_failure`): zawierają informacje o nieudanych próbach rozpoczęcia rozmowy, w tym przyczyny błędów i metadane

## Włączanie webhooków po rozmowie

Webhooki po rozmowie możesz włączyć dla wszystkich agentów w swoim obszarze roboczym na [stronie ustawień](https://elevenlabs.io/app/agents/settings) ElevenAgents.

![Ustawienia webhooków po rozmowie](/docs/_fern-img/eb5d768612d6461a21bc3127611f60724be3e1a55005af43faf48f2d7bf23807.webp)

> **Warning**
>
> Webhooki po rozmowie muszą zwracać kod statusu 200, aby zostały uznane za udane. Webhooki, które
> wielokrotnie zawodzą, są automatycznie wyłączane, jeśli wystąpi co najmniej 10 kolejnych błędów, a ostatnie
> udane dostarczenie nastąpiło ponad 7 dni temu lub nigdy nie nastąpiło.

> **Note**
>
> Webhooki po rozmowie mogą być automatycznie ponawiane w razie niepowodzenia. Zobacz [ponowienia webhooków](/docs/pl/eleven-api/resources/webhooks#retries).

### Uwierzytelnianie

Ważne, by odbiornik weryfikował wszystkie przychodzące webhooki. Webhooki obecnie obsługują uwierzytelnianie za pomocą podpisów HMAC. Aby skonfigurować uwierzytelnianie HMAC:

* Bezpiecznie przechowuj współdzielony sekret wygenerowany podczas tworzenia webhooka
* Zweryfikuj nagłówek ElevenLabs-Signature w swoim endpointzie za pomocą SDK

SDK JavaScript udostępnia `constructEvent`, a SDK Python `construct_event` z parametrami **`rawBody`**, **`sig_header`** i **`secret`** (w Pythonie nie nazywają się one `payload` / `signature`). Oba weryfikują podpis, sprawdzają znacznik czasu i parsują dane JSON.

#### Python

Przykładowy handler webhooka z użyciem 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

Przykładowy handler webhooka z użyciem 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

Przykładowy handler webhooka z użyciem trasy API Next.js:

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

### Lista dozwolonych adresów IP

Dla dodatkowego bezpieczeństwa możesz dodać statyczne wychodzące adresy IP ElevenLabs do listy dozwolonych adresów. Pełną listę adresów IP znajdziesz w sekcji [Lista dozwolonych adresów IP](/docs/pl/eleven-api/resources/ip-allowlisting).

> **Tip**
>
> Używanie listy dozwolonych adresów IP razem z walidacją podpisu HMAC zapewnia wiele warstw
> zabezpieczeń.

## Struktura odpowiedzi webhooka

ElevenLabs wysyła trzy różne typy webhooków po rozmowie, każdy z inną strukturą danych:

### Webhooki transkrypcji (`post_call_transcription`)

Zawierają pełne dane rozmowy, w tym kompletne transkrypcje, wyniki analizy i metadane.

#### Pola najwyższego poziomu

| Pole              | Typ    | Opis                                                       |
| ----------------- | ------ | ---------------------------------------------------------- |
| `type`            | string | Typ zdarzenia (zawsze `post_call_transcription`)           |
| `data`            | object | Dane rozmowy w strukturze `ConversationHistoryCommonModel` |
| `event_timestamp` | number | Czas wystąpienia zdarzenia w formacie Unix UTC             |

#### Struktura obiektu danych

Obiekt `data` zawiera:

| Pole                                  | Typ     | Opis                                                       |
| ------------------------------------- | ------- | ---------------------------------------------------------- |
| `agent_id`                            | string  | ID agenta, który obsłużył rozmowę                          |
| `agent_name`                          | string  | Nazwa agenta w czasie rozmowy                              |
| `conversation_id`                     | string  | Unikalny identyfikator rozmowy                             |
| `status`                              | string  | Status rozmowy (np. "done")                                |
| `user_id`                             | string  | Identyfikator użytkownika, jeśli jest dostępny             |
| `branch_id`                           | string  | Gałąź agenta użyta w rozmowie, jeśli dotyczy               |
| `version_id`                          | string  | ID wersji agenta (migawki) aktywnej podczas rozmowy        |
| `environment`                         | string  | Środowisko użyte do rozwiązywania zmiennych środowiskowych |
| `transcript`                          | array   | Pełna transkrypcja rozmowy z turami                        |
| `metadata`                            | object  | Czas rozmowy, koszty i dane telefonu                       |
| `analysis`                            | object  | Wyniki oceny i podsumowanie rozmowy                        |
| `conversation_initiation_client_data` | object  | Zastąpienia konfiguracji i zmienne dynamiczne              |
| `has_audio`                           | boolean | Czy dla rozmowy dostępne jest audio                        |
| `has_user_audio`                      | boolean | Czy dla rozmowy dostępne jest audio użytkownika            |
| `has_response_audio`                  | boolean | Czy dla rozmowy dostępne jest audio odpowiedzi agenta      |

### Webhooki audio (`post_call_audio`)

Zawierają minimalny zestaw danych oraz pełne audio rozmowy w formacie MP3 zakodowane base64.

#### Pola najwyższego poziomu

| Pole              | Typ    | Opis                                           |
| ----------------- | ------ | ---------------------------------------------- |
| `type`            | string | Typ zdarzenia (zawsze `post_call_audio`)       |
| `data`            | object | Minimalne dane audio                           |
| `event_timestamp` | number | Czas wystąpienia zdarzenia w formacie Unix UTC |

#### Struktura obiektu danych

Obiekt `data` zawiera tylko:

| Pole              | Typ    | Opis                                                         |
| ----------------- | ------ | ------------------------------------------------------------ |
| `agent_id`        | string | ID agenta, który obsłużył rozmowę                            |
| `conversation_id` | string | Unikalny identyfikator rozmowy                               |
| `full_audio`      | string | Ciąg zakodowany base64 z pełnym audio rozmowy w formacie MP3 |

> **Warning**
>
> Webhooki audio zawierają tylko trzy pola wymienione powyżej. NIE zawierają danych transkrypcji,
> metadanych, wyników analizy ani żadnych innych szczegółów rozmowy.

### Webhooki błędów inicjowania rozmowy (`call_initiation_failure`)

Zawierają informacje o próbach inicjowania połączeń telefonicznych, w tym przyczyny błędów i metadane dostawcy telefonii.

> **Note**
>
> Zdarzenia webhooka błędu inicjowania rozmowy są wysyłane, gdy nie uda się rozpocząć rozmowy z powodu
> błędów połączenia, odrzucenia rozmowy przez użytkownika lub nieodebrania jej przez użytkownika. Jeśli rozmowa trafi na pocztę głosową lub
> zostanie odebrana przez automatyczną usługę, webhook błędu inicjowania rozmowy nie jest wysyłany, ponieważ rozmowa
> została pomyślnie rozpoczęta.

#### Pola najwyższego poziomu

| Pole              | Typ    | Opis                                             |
| ----------------- | ------ | ------------------------------------------------ |
| `type`            | string | Typ zdarzenia (zawsze `call_initiation_failure`) |
| `data`            | object | Dane błędu inicjowania rozmowy                   |
| `event_timestamp` | number | Czas wystąpienia zdarzenia w formacie Unix UTC   |

#### Struktura obiektu danych

Obiekt `data` zawiera:

| Pole              | Typ    | Opis                                                |
| ----------------- | ------ | --------------------------------------------------- |
| `agent_id`        | string | ID agenta przypisanego do obsługi rozmowy           |
| `conversation_id` | string | Unikalny identyfikator rozmowy                      |
| `failure_reason`  | string | Przyczyna błędu ("busy", "no-answer", "unknown")    |
| `metadata`        | object | Dodatkowe dane przekazane przez dostawcę telefonii. |

W przypadku połączeń wychodzących przez Twilio `failure_reason` pochodzi ze stanu `CallStatus` Twilio: wartości `busy` i `no-answer` są przekazywane bez zmian, a `failed` jest zgłaszane jako `unknown`. Inne statusy połączeń Twilio nie wysyłają webhooka błędu inicjowania rozmowy.

#### Struktura obiektu metadanych

Struktura obiektu `metadata` zależy od tego, czy połączenie wychodzące wykonano przez Twilio czy przez trunking SIP. Obiekt zawiera pole `type`, które rozróżnia te dwie opcje, oraz pole `body` ze szczegółami zależnymi od dostawcy.

**Metadane SIP** (`type: "sip"`):

| Pole   | Typ    | Wymagane | Opis                                |
| ------ | ------ | -------- | ----------------------------------- |
| `type` | string | Tak      | Typ dostawcy (zawsze `sip`)         |
| `body` | object | Tak      | Informacje o błędzie połączenia SIP |

Obiekt `body` metadanych SIP zawiera:

| Pole              | Typ    | Wymagane | Opis                                                                                             |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------ |
| `from_number`     | number | Tak      | Numer telefonu strony, która zainicjowała rozmowę.                                               |
| `to_number`       | number | Tak      | Numer telefonu strony, do której wykonywane jest połączenie.                                     |
| `sip_status_code` | number | Tak      | Kod statusu odpowiedzi SIP (np. 486 dla zajętej linii)                                           |
| `error_reason`    | string | Tak      | Opis błędu zrozumiały dla człowieka                                                              |
| `call_sid`        | string | Tak      | Identyfikator sesji połączenia SIP                                                               |
| `twirp_code`      | string | Nie      | [Kod błędu Twirp](https://twitchtv.github.io/twirp/docs/spec_v7.html#error-codes), jeśli dotyczy |
| `sip_status`      | string | Nie      | Tekst statusu SIP odpowiadający kodowi statusu                                                   |

**Metadane Twilio** (`type: "twilio"`):

| Pole   | Typ    | Wymagane | Opis                                                                                                                                    |
| ------ | ------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Tak      | Typ dostawcy (zawsze `twilio`)                                                                                                          |
| `body` | object | Tak      | Treść StatusCallback Twilio ze szczegółami rozmowy, opisana [tutaj](https://www.twilio.com/docs/voice/api/call-resource#statuscallback) |

## Przykładowe ładunki webhooków

### Przykład webhooka transkrypcji

```json
{
  "type": "post_call_transcription",
  "event_timestamp": 1739537297,
  "data": {
    "agent_id": "xyz",
    "conversation_id": "abc",
    "status": "done",
    "user_id": "user123",
    "transcript": [
      {
        "role": "agent",
        "message": "Hey there angelo. How are you?",
        "tool_calls": null,
        "tool_results": null,
        "feedback": null,
        "time_in_call_secs": 0,
        "conversation_turn_metrics": null
      },
      {
        "role": "user",
        "message": "Hey, can you tell me, like, a fun fact about 11 Labs?",
        "tool_calls": null,
        "tool_results": null,
        "feedback": null,
        "time_in_call_secs": 2,
        "conversation_turn_metrics": null
      },
      {
        "role": "agent",
        "message": "I do not have access to fun facts about Eleven Labs. However, I can share some general information about the company. Eleven Labs is an AI voice technology platform that specializes in voice cloning and text-to-speech...",
        "tool_calls": null,
        "tool_results": null,
        "feedback": null,
        "time_in_call_secs": 9,
        "conversation_turn_metrics": {
          "convai_llm_service_ttfb": {
            "elapsed_time": 0.3704247010173276
          },
          "convai_llm_service_ttf_sentence": {
            "elapsed_time": 0.5551181449554861
          }
        }
      }
    ],
    "metadata": {
      "start_time_unix_secs": 1739537297,
      "call_duration_secs": 22,
      "cost": 296,
      "deletion_settings": {
        "deletion_time_unix_secs": 1802609320,
        "deleted_logs_at_time_unix_secs": null,
        "deleted_audio_at_time_unix_secs": null,
        "deleted_transcript_at_time_unix_secs": null,
        "delete_transcript_and_pii": true,
        "delete_audio": true
      },
      "feedback": {
        "overall_score": null,
        "likes": 0,
        "dislikes": 0
      },
      "authorization_method": "authorization_header",
      "charging": {
        "dev_discount": true
      },
      "termination_reason": ""
    },
    "analysis": {
      "evaluation_criteria_results": {},
      "data_collection_results": {},
      "call_successful": "success",
      "transcript_summary": "The conversation begins with the agent asking how Angelo is, but Angelo redirects the conversation by requesting a fun fact about 11 Labs. The agent acknowledges they don't have specific fun facts about Eleven Labs but offers to provide general information about the company. They briefly describe Eleven Labs as an AI voice technology platform specializing in voice cloning and text-to-speech technology. The conversation is brief and informational, with the agent adapting to the user's request despite not having the exact information asked for."
    },
    "conversation_initiation_client_data": {
      "conversation_config_override": {
        "agent": {
          "prompt": null,
          "first_message": null,
          "language": "en"
        },
        "tts": {
          "voice_id": null
        }
      },
      "custom_llm_extra_body": {},
      "dynamic_variables": {
        "user_name": "angelo"
      },
      "branch_id": null,
      "environment": null
    }
  }
}
```

### Przykład webhooka audio

```json
{
  "type": "post_call_audio",
  "event_timestamp": 1739537319,
  "data": {
    "agent_id": "xyz",
    "conversation_id": "abc",
    "full_audio": "SUQzBAAAAAAA...base64_encoded_mp3_data...AAAAAAAAAA=="
  }
}
```

### Przykłady webhooków błędu inicjowania rozmowy

#### Przykład metadanych Twilio

```json
{
  "type": "call_initiation_failure",
  "event_timestamp": 1759931652,
  "data": {
    "agent_id": "xyz",
    "conversation_id": "abc",
    "failure_reason": "busy",
    "metadata": {
      "type": "twilio",
      "body": {
        "Called": "+441111111111",
        "ToState": "",
        "CallerCountry": "US",
        "Direction": "outbound-api",
        "Timestamp": "Wed, 08 Oct 2025 13:54:12 +0000",
        "CallbackSource": "call-progress-events",
        "SipResponseCode": "487",
        "CallerState": "WA",
        "ToZip": "",
        "SequenceNumber": "2",
        "CallSid": "CA8367245817625617832576245724",
        "To": "+441111111111",
        "CallerZip": "98631",
        "ToCountry": "GB",
        "CalledZip": "",
        "ApiVersion": "2010-04-01",
        "CalledCity": "",
        "CallStatus": "busy",
        "Duration": "0",
        "From": "+11111111111",
        "CallDuration": "0",
        "AccountSid": "AC37682153267845716245762454a",
        "CalledCountry": "GB",
        "CallerCity": "RAYMOND",
        "ToCity": "",
        "FromCountry": "US",
        "Caller": "+11111111111",
        "FromCity": "RAYMOND",
        "CalledState": "",
        "FromZip": "12345",
        "FromState": "WA"
      }
    }
  }
}
```

#### Przykład metadanych SIP

```json
{
  "type": "call_initiation_failure",
  "event_timestamp": 1759931652,
  "data": {
    "agent_id": "xyz",
    "conversation_id": "abc",
    "failure_reason": "busy",
    "metadata": {
      "type": "sip",
      "body": {
        "from_number": "+441111111111",
        "to_number": "+11111111111",
        "sip_status_code": 486,
        "error_reason": "INVITE failed: sip status: 486: Busy here (SIP 486)",
        "call_sid": "d8e7f6a5-b4c3-4d5e-8f9a-0b1c2d3e4f5a",
        "sip_status": "Busy here",
        "twirp_code": "unavailable"
      }
    }
  }
}
```

## Dostarczanie webhooków audio

Webhooki audio są dostarczane osobno od webhooków transkrypcji i zawierają tylko podstawowe pola potrzebne do identyfikacji rozmowy oraz dane audio zakodowane base64.

> **Note**
>
> Webhooki audio możesz włączyć lub wyłączyć przełącznikiem „Send audio data” w ustawieniach
> webhooka. Możesz ustawić to na poziomie workspace'u (w ustawieniach ElevenAgents)
> oraz na poziomie agenta (w indywidualnych zastąpieniach webhooków agenta).

### Dostarczanie strumieniowe

Webhooki audio są dostarczane jako strumieniowe żądania HTTP z nagłówkiem `transfer-encoding: chunked`, aby sprawnie obsługiwać duże pliki audio. Każde żądanie ma limit czasu 5 minut.

### Ponowne próby

Gdy dla webhooka włączone są ponowne próby, nieudane dostarczenia audio są ponawiane według tego samego harmonogramu co webhooki transkrypcji. Ponowna próba wysyła cały ładunek audio, więc usuwaj duplikaty według `conversation_id`. Harmonogram, błędy kwalifikujące się do ponownej próby i limity rozmiaru audio znajdziesz w sekcji [ponowne próby webhooków](/docs/pl/eleven-api/resources/webhooks#retries).

### Przetwarzanie webhooków audio

Webhooki audio są dostarczane z użyciem kodowania transferu fragmentowego, dlatego musisz prawidłowo obsłużyć dane strumieniowe:

```python

import base64
import json
from aiohttp import web

async def handle_webhook(request):

    # Check if this is a chunked/streaming request
    if request.headers.get("transfer-encoding", "").lower() == "chunked":
        # Read streaming data in chunks
        chunked_body = bytearray()
        while True:
            chunk = await request.content.read(8192)  # 8KB chunks
            if not chunk:
                break
            chunked_body.extend(chunk)

        # Parse the complete payload
        request_body = json.loads(chunked_body.decode("utf-8"))
    else:
        # Handle regular requests
        body_bytes = await request.read()
        request_body = json.loads(body_bytes.decode('utf-8'))

    # Process different webhook types
    if request_body["type"] == "post_call_transcription":
        # Handle transcription webhook with full conversation data
        handle_transcription_webhook(request_body["data"])
    elif request_body["type"] == "post_call_audio":
        # Handle audio webhook with minimal data
        handle_audio_webhook(request_body["data"])
    elif request_body["type"] == "call_initiation_failure":
        # Handle call initiation failure webhook
        handle_call_initiation_failure_webhook(request_body["data"])

    return web.json_response({"status": "ok"})

def handle_audio_webhook(data):
    # Decode base64 audio data
    audio_bytes = base64.b64decode(data["full_audio"])

    # Save or process the audio file
    conversation_id = data["conversation_id"]
    with open(f"conversation_{conversation_id}.mp3", "wb") as f:
        f.write(audio_bytes)

def handle_call_initiation_failure_webhook(data):
    # Handle call initiation failure events
    agent_id = data["agent_id"]
    conversation_id = data["conversation_id"]
    failure_reason = data.get("failure_reason")
    metadata = data.get("metadata", {})

    # Log the failure for monitoring
    print(f"Call failed for agent {agent_id}, conversation {conversation_id}")
    print(f"Failure reason: {failure_reason}")

    # Access provider-specific metadata
    provider_type = metadata.get("type")
    body = metadata.get("body", {})
    if provider_type == "sip":
        print(f"SIP status code: {body.get('sip_status_code')}")
        print(f"Error reason: {body.get('error_reason')}")
    elif provider_type == "twilio":
        print(f"Twilio CallSid: {body.get('CallSid')}")
        print(f"Call status: {body.get('CallStatus')}")

    # Update your system with the failure information
    # e.g., mark lead as "call_failed" in CRM

```

```javascript
import fs from "fs";

app.post("/webhook/elevenlabs", (req, res) => {
  let body = "";

  // Handle chunked/streaming requests
  req.on("data", (chunk) => {
    body += chunk;
  });

  req.on("end", () => {
    try {
      const requestBody = JSON.parse(body);

      // Process different webhook types
      if (requestBody.type === "post_call_transcription") {
        // Handle transcription webhook with full conversation data
        handleTranscriptionWebhook(requestBody.data);
      } else if (requestBody.type === "post_call_audio") {
        // Handle audio webhook with minimal data
        handleAudioWebhook(requestBody.data);
      } else if (requestBody.type === "call_initiation_failure") {
        // Handle call initiation failure webhook
        handleCallFailureWebhook(requestBody.data);
      }

      res.status(200).json({ status: "ok" });
    } catch (error) {
      console.error("Error processing webhook:", error);
      res.status(400).json({ error: "Invalid JSON" });
    }
  });
});

function handleAudioWebhook(data) {
  // Decode base64 audio data
  const audioBytes = Buffer.from(data.full_audio, "base64");

  // Save or process the audio file
  const conversationId = data.conversation_id;
  fs.writeFileSync(`conversation_${conversationId}.mp3`, audioBytes);
}

function handleCallFailureWebhook(data) {
  // Handle call initiation failure events
  const { agent_id, conversation_id, failure_reason, metadata } = data;

  // Log the failure for monitoring
  console.log(`Call failed for agent ${agent_id}, conversation ${conversation_id}`);
  console.log(`Failure reason: ${failure_reason}`);

  // Access provider-specific metadata
  const body = metadata.body || {};
  if (metadata?.type === "sip") {
    console.log(`SIP status code: ${body.sip_status_code}`);
    console.log(`Error reason: ${body.error_reason}`);
  } else if (metadata?.type === "twilio") {
    console.log(`Twilio CallSid: ${body.CallSid}`);
    console.log(`Call status: ${body.CallStatus}`);
  }

  // Update your system with the failure information
  // e.g., mark lead as "call_failed" in CRM
}
```

> **Note**
>
> Webhooki audio mogą być dużymi plikami, więc upewnij się, że endpoint webhooka obsługuje żądania strumieniowe
> i ma wystarczającą ilość pamięci lub miejsca na dane. Audio jest dostarczane w formacie MP3.

## Zastosowania

### Automatyczne działania po rozmowie

Webhooki po rozmowie pozwalają tworzyć automatyczne workflow uruchamiane zaraz po zakończeniu rozmowy. Oto kilka praktycznych zastosowań:

#### Integracja z CRM

Zaktualizuj system zarządzania relacjami z klientami o dane rozmowy zaraz po jej zakończeniu:

```javascript
// Example webhook handler
app.post("/webhook/elevenlabs", async (req, res) => {
  // HMAC validation code

  const { data } = req.body;

  // Extract key information
  const userId = data.metadata.user_id;
  const transcriptSummary = data.analysis.transcript_summary;
  const callSuccessful = data.analysis.call_successful;

  // Update CRM record
  await updateCustomerRecord(userId, {
    lastInteraction: new Date(),
    conversationSummary: transcriptSummary,
    callOutcome: callSuccessful,
    fullTranscript: data.transcript,
  });

  res.status(200).send("Webhook received");
});
```

### Rozmowy ze stanem

Zachowaj kontekst rozmowy między wieloma interakcjami, zapisując i odczytując stan:

1. Gdy rozmowa się rozpoczyna, przekaż identyfikator użytkownika jako zmienną dynamiczną.
2. Gdy rozmowa się kończy, skonfiguruj endpoint webhooka tak, aby zapisywał dane rozmowy w bazie danych na podstawie identyfikatora użytkownika wyodrębnionego z dynamic_variables.
3. Gdy użytkownik zadzwoni ponownie, możesz pobrać ten kontekst i przekazać go do nowej rozmowy jako zmienną dynamiczną `{{previous_topics}}`.
4. Dzięki temu agent „pamięta” wcześniejsze interakcje.

```javascript
// Store conversation state when call ends
app.post("/webhook/elevenlabs", async (req, res) => {
  // HMAC validation code

  const { data } = req.body;
  const userId = data.metadata.user_id;

  // Store conversation state
  await db.userStates.upsert({
    userId,
    lastConversationId: data.conversation_id,
    lastInteractionTimestamp: data.metadata.start_time_unix_secs,
    conversationHistory: data.transcript,
    previousTopics: extractTopics(data.analysis.transcript_summary),
  });

  res.status(200).send("Webhook received");
});

// When initiating a new call, retrieve and use the state
async function initiateCall(userId) {
  // Get user's conversation state
  const userState = await db.userStates.findOne({ userId });

  // Start new conversation with context from previous calls
  return await elevenlabs.startConversation({
    agent_id: "xyz",
    conversation_id: generateNewId(),
    dynamic_variables: {
      user_name: userState.name,
      previous_conversation_id: userState.lastConversationId,
      previous_topics: userState.previousTopics.join(", "),
    },
  });
}
```