> 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 pós-chamada

## Visão geral

Os [webhooks](/docs/pt/eleven-api/resources/webhooks) pós-chamada permitem receber informações detalhadas sobre uma chamada após a conclusão da análise. Quando ativados, a ElevenLabs envia uma solicitação POST ao endpoint especificado com dados completos da chamada.

A ElevenLabs oferece suporte a três tipos de webhooks pós-chamada:

* **Webhooks de transcrição** (`post_call_transcription`): contêm dados completos da conversa, incluindo transcrições, resultados de análise e metadados
* **Webhooks de áudio** (`post_call_audio`): contêm dados mínimos com o áudio de toda a conversa codificado em base64
* **Webhooks de falha na iniciação da chamada** (`call_initiation_failure`): contêm informações sobre tentativas de iniciar chamadas que falharam, incluindo motivos da falha e metadados

## Ativando webhooks pós-chamada

Os webhooks pós-chamada podem ser ativados para todos os agentes do seu workspace pela [página de configurações](https://elevenlabs.io/app/agents/settings) do ElevenAgents.

![Configurações do webhook pós-chamada](/docs/_fern-img/eb5d768612d6461a21bc3127611f60724be3e1a55005af43faf48f2d7bf23807.webp)

> **Warning**
>
> Os webhooks pós-chamada devem retornar um código de status 200 para serem considerados bem-sucedidos. Webhooks que
> falham repetidamente são desativados automaticamente se houver 10 ou mais falhas consecutivas e a última
> entrega bem-sucedida tiver ocorrido há mais de 7 dias ou nunca tiver sido entregue com sucesso.

> **Note**
>
> Os webhooks pós-chamada podem receber novas tentativas automaticamente se falharem. Consulte [novas tentativas de webhook](/docs/pt/eleven-api/resources/webhooks#retries).

### Autenticação

É importante que o listener valide todos os webhooks recebidos. Atualmente, os webhooks oferecem suporte à autenticação por assinaturas HMAC. Configure a autenticação HMAC:

* Armazenando com segurança o segredo compartilhado gerado na criação do webhook
* Verificando o cabeçalho ElevenLabs-Signature no seu endpoint usando o SDK

O SDK JavaScript disponibiliza `constructEvent`; o SDK Python disponibiliza `construct_event` com **`rawBody`**, **`sig_header`** e **`secret`** (em Python, eles não se chamam `payload` / `signature`). Ambos verificam a assinatura, validam o carimbo de data e hora e analisam o payload JSON.

#### Python

Exemplo de manipulador de webhook usando 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

Exemplo de manipulador de webhook usando 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

Exemplo de manipulador de webhook usando uma rota de API do 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 de permissões de IP

Para maior segurança, você pode adicionar os IPs de saída estáticos da ElevenLabs à sua lista de permissões. Consulte [Lista de permissões de IP](/docs/pt/eleven-api/resources/ip-allowlisting) para ver a lista completa de endereços IP.

> **Tip**
>
> Usar uma lista de permissões de IP junto com a validação de assinatura HMAC oferece várias camadas de
> segurança.

## Estrutura da resposta do webhook

A ElevenLabs envia três tipos distintos de webhooks pós-chamada, cada um com estruturas de dados diferentes:

### Webhooks de transcrição (`post_call_transcription`)

Contêm dados completos da conversa, incluindo transcrições integrais, resultados de análise e metadados.

#### Campos de nível superior

| Campo             | Tipo   | Descrição                                                             |
| ----------------- | ------ | --------------------------------------------------------------------- |
| `type`            | string | Tipo de evento (sempre `post_call_transcription`)                     |
| `data`            | object | Dados da conversa usando a estrutura `ConversationHistoryCommonModel` |
| `event_timestamp` | number | Quando este evento ocorreu, em horário Unix UTC                       |

#### Estrutura do objeto de dados

O objeto `data` contém:

| Campo                                 | Tipo    | Descrição                                                           |
| ------------------------------------- | ------- | ------------------------------------------------------------------- |
| `agent_id`                            | string  | ID do agente que atendeu a chamada                                  |
| `agent_name`                          | string  | Nome do agente no momento da conversa                               |
| `conversation_id`                     | string  | Identificador exclusivo da conversa                                 |
| `status`                              | string  | Status da conversa (por exemplo, "done")                            |
| `user_id`                             | string  | Identificador do usuário, se disponível                             |
| `branch_id`                           | string  | Ramificação do agente usada na conversa, se aplicável               |
| `version_id`                          | string  | ID da versão do agente (snapshot) ativa durante a chamada           |
| `environment`                         | string  | Ambiente usado para resolver variáveis de ambiente                  |
| `transcript`                          | array   | Transcrição completa da conversa com os turnos                      |
| `metadata`                            | object  | Informações de tempo, custos e telefone da chamada                  |
| `analysis`                            | object  | Resultados da avaliação e resumo da conversa                        |
| `conversation_initiation_client_data` | object  | Substituições de configuração e variáveis dinâmicas                 |
| `has_audio`                           | boolean | Indica se há áudio disponível para a conversa                       |
| `has_user_audio`                      | boolean | Indica se o áudio do usuário está disponível para a conversa        |
| `has_response_audio`                  | boolean | Indica se o áudio da resposta do agente está disponível na conversa |

### Webhooks de áudio (`post_call_audio`)

Contêm dados mínimos com o áudio completo da conversa em MP3 codificado em base64.

#### Campos de nível superior

| Campo             | Tipo   | Descrição                                       |
| ----------------- | ------ | ----------------------------------------------- |
| `type`            | string | Tipo de evento (sempre `post_call_audio`)       |
| `data`            | object | Dados mínimos de áudio                          |
| `event_timestamp` | number | Quando este evento ocorreu, em horário Unix UTC |

#### Estrutura do objeto de dados

O objeto `data` contém apenas:

| Campo             | Tipo   | Descrição                                                                   |
| ----------------- | ------ | --------------------------------------------------------------------------- |
| `agent_id`        | string | ID do agente que atendeu a chamada                                          |
| `conversation_id` | string | Identificador exclusivo da conversa                                         |
| `full_audio`      | string | String codificada em base64 com o áudio completo da conversa em formato MP3 |

> **Warning**
>
> Os webhooks de áudio contêm apenas os três campos listados acima. Eles NÃO incluem dados de transcrição,
> metadados, resultados de análise nem outros detalhes da conversa.

### Webhooks de falha na iniciação da chamada (`call_initiation_failure`)

Contêm informações sobre tentativas de iniciar chamadas telefônicas, incluindo motivos de falha e metadados do provedor de telefonia.

> **Note**
>
> Os eventos de webhook de falha na iniciação da chamada são enviados quando uma chamada não pode ser iniciada devido a erros de conexão,
> à recusa da chamada pelo usuário ou porque o usuário não atende. Se uma chamada cair na caixa postal ou for atendida
> por um serviço automatizado, nenhum webhook de falha na iniciação será enviado, pois a chamada foi
> iniciada com sucesso.

#### Campos de nível superior

| Campo             | Tipo   | Descrição                                         |
| ----------------- | ------ | ------------------------------------------------- |
| `type`            | string | Tipo de evento (sempre `call_initiation_failure`) |
| `data`            | object | Dados de falha na iniciação da chamada            |
| `event_timestamp` | number | Quando este evento ocorreu, em horário Unix UTC   |

#### Estrutura do objeto de dados

O objeto `data` contém:

| Campo             | Tipo   | Descrição                                               |
| ----------------- | ------ | ------------------------------------------------------- |
| `agent_id`        | string | ID do agente designado para atender a chamada           |
| `conversation_id` | string | Identificador exclusivo da conversa                     |
| `failure_reason`  | string | Motivo da falha ("busy", "no-answer", "unknown")        |
| `metadata`        | object | Dados adicionais fornecidos pelo provedor de telefonia. |

#### Estrutura do objeto de metadados

A estrutura do objeto `metadata` varia dependendo se a chamada de saída foi feita pelo Twilio ou por entroncamento SIP. O objeto inclui um campo `type`, que diferencia os dois, e um campo `body`, que contém detalhes específicos do provedor.

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

| Campo  | Tipo   | Obrigatório | Descrição                                          |
| ------ | ------ | ----------- | -------------------------------------------------- |
| `type` | string | Sim         | Tipo de provedor (sempre `sip`)                    |
| `body` | object | Sim         | Informações de falha de chamada específicas do SIP |

O objeto `body` dos metadados SIP contém:

| Campo             | Tipo   | Obrigatório | Descrição                                                                                            |
| ----------------- | ------ | ----------- | ---------------------------------------------------------------------------------------------------- |
| `from_number`     | number | Sim         | Número de telefone da parte que iniciou a chamada.                                                   |
| `to_number`       | number | Sim         | Número de telefone da parte chamada.                                                                 |
| `sip_status_code` | number | Sim         | Código de status da resposta SIP (por exemplo, 486 para ocupado)                                     |
| `error_reason`    | string | Sim         | Descrição do erro legível por humanos                                                                |
| `call_sid`        | string | Sim         | Identificador da sessão de chamada SIP                                                               |
| `twirp_code`      | string | Não         | [Código de erro Twirp](https://twitchtv.github.io/twirp/docs/spec_v7.html#error-codes), se aplicável |
| `sip_status`      | string | Não         | Texto de status SIP correspondente ao código de status                                               |

**Metadados do Twilio** (`type: "twilio"`):

| Campo  | Tipo   | Obrigatório | Descrição                                                                                                                                         |
| ------ | ------ | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `type` | string | Sim         | Tipo de provedor (sempre `twilio`)                                                                                                                |
| `body` | object | Sim         | Corpo do StatusCallback do Twilio com detalhes da chamada, documentado [aqui](https://www.twilio.com/docs/voice/api/call-resource#statuscallback) |

## Exemplos de payloads de webhook

### Exemplo de webhook de transcrição

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

### Exemplo de webhook de áudio

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

### Exemplos de webhook de falha na iniciação da chamada

#### Exemplo de metadados do 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"
      }
    }
  }
}
```

#### Exemplo de metadados 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"
      }
    }
  }
}
```

## Entrega de webhooks de áudio

Os webhooks de áudio são entregues separadamente dos webhooks de transcrição e contêm apenas os campos essenciais para identificar a conversa, junto com os dados de áudio codificados em base64.

> **Note**
>
> Os webhooks de áudio podem ser ativados ou desativados usando a opção "Enviar dados de áudio" nas configurações
> do webhook. Essa configuração pode ser definida tanto no nível do espaço de trabalho (nas configurações do ElevenAgents)
> quanto no nível do agente (nas substituições de webhook de cada agente).

### Entrega por streaming

Os webhooks de áudio são entregues como solicitações HTTP em streaming com o cabeçalho `transfer-encoding: chunked`, para processar arquivos de áudio grandes com eficiência. Cada solicitação expira após 5 minutos.

### Tentativas

Quando as tentativas estão ativadas no webhook, as entregas de áudio que falharem são repetidas na mesma programação dos webhooks de transcrição. Uma nova tentativa reenvia o payload de áudio completo, portanto, elimine duplicatas usando `conversation_id`. Consulte [tentativas de webhook](/docs/pt/eleven-api/resources/webhooks#retries) para ver a programação, os erros que permitem novas tentativas e os limites de tamanho de áudio.

### Processamento de webhooks de áudio

Como os webhooks de áudio são entregues por codificação de transferência em blocos, você precisa processar corretamente os dados em streaming:

```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**
>
> Os webhooks de áudio podem ser arquivos grandes, portanto, garanta que seu endpoint de webhook possa processar solicitações em streaming
> e tenha capacidade suficiente de memória/armazenamento. O áudio é entregue no formato MP3.

## Casos de uso

### Acompanhamentos automatizados de chamadas

Os webhooks pós-chamada permitem criar workflows automatizados que são acionados imediatamente após o término de uma chamada. Veja algumas aplicações práticas:

#### Integração com CRM

Atualize seu sistema de gestão de relacionamento com clientes com os dados da conversa assim que uma chamada for concluída:

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

### Conversas com estado

Mantenha o contexto da conversa entre várias interações armazenando e recuperando o estado:

1. Quando uma chamada começar, envie o ID do usuário como uma variável dinâmica.
2. Quando uma chamada terminar, configure seu endpoint de webhook para armazenar os dados da conversa no banco de dados com base no ID de usuário extraído de dynamic_variables.
3. Quando o usuário ligar novamente, você poderá recuperar esse contexto e enviá-lo para a nova conversa em uma variável dinâmica \{\{previous_topics}}.
4. Isso cria uma experiência contínua, em que o agente "lembra" das interações anteriores.

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