Webhooks posteriores a la llamada

Recibe notificaciones cuando terminen las llamadas y se complete el análisis mediante webhooks.

Resumen

Los webhooks posteriores a la llamada te permiten recibir información detallada sobre una llamada cuando se complete el análisis. Cuando están activados, ElevenLabs enviará una solicitud POST a la ruta que especifiques con datos completos de la llamada.

ElevenLabs admite tres tipos de webhooks posteriores a la llamada:

  • Webhooks de transcripción (post_call_transcription): incluyen todos los datos de la conversación, como transcripciones, resultados de análisis y metadatos.
  • Webhooks de audio (post_call_audio): incluyen datos mínimos con el audio de toda la conversación codificado en base64.
  • Webhooks de error al iniciar la llamada (call_initiation_failure): incluyen información sobre intentos fallidos de iniciar llamadas, como los motivos del error y metadatos.

Activar webhooks posteriores a la llamada

Puedes activar los webhooks posteriores a la llamada para todos los agentes de tu espacio de trabajo desde la página de configuración de ElevenAgents.

Configuración de webhooks posteriores a la llamada

Los webhooks posteriores a la llamada deben devolver un código de estado 200 para considerarse correctos. Los webhooks que fallan repetidamente se desactivan automáticamente si acumulan 10 o más errores consecutivos y la última entrega correcta fue hace más de 7 días o nunca se han entregado correctamente.

Los webhooks posteriores a la llamada pueden reintentarse automáticamente si fallan. Consulta los reintentos de webhooks.

Autenticación

Es importante que el listener valide todos los webhooks entrantes. Actualmente, los webhooks admiten autenticación mediante firmas HMAC. Configura la autenticación HMAC de esta forma:

  • Almacena de forma segura el secreto compartido generado al crear el webhook
  • Verifica la cabecera ElevenLabs-Signature en tu ruta de API mediante el SDK

El SDK de JavaScript incluye constructEvent; el SDK de Python incluye construct_event con rawBody, sig_header y secret (en Python no se llaman payload / signature). Ambos verifican la firma, validan la marca de tiempo y analizan la carga útil JSON.

Ejemplo de controlador de webhook con FastAPI:

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

Lista de direcciones IP permitidas

Para mayor seguridad, puedes añadir las IP de salida estáticas de ElevenLabs a tu lista de direcciones permitidas. Consulta la lista de direcciones IP permitidas para ver la lista completa de direcciones IP.

Usar una lista de direcciones IP permitidas junto con la validación de firmas HMAC proporciona varias capas de seguridad.

Estructura de la respuesta del webhook

ElevenLabs envía tres tipos distintos de webhooks posteriores a la llamada, cada uno con estructuras de datos diferentes:

Webhooks de transcripción (post_call_transcription)

Incluyen datos completos de la conversación, como transcripciones completas, resultados de análisis y metadatos.

Campos de nivel superior

CampoTipoDescripción
typecadenaTipo de evento (siempre post_call_transcription)
dataobjetoDatos de la conversación con la estructura ConversationHistoryCommonModel
event_timestampnúmeroCuándo ocurrió este evento en tiempo Unix UTC

Estructura del objeto de datos

El objeto data contiene:

CampoTipoDescripción
agent_idcadenaEl ID del agente que gestionó la llamada
agent_namecadenaEl nombre del agente en el momento de la conversación
conversation_idcadenaIdentificador único de la conversación
statuscadenaEstado de la conversación (p. ej., “done”)
user_idcadenaIdentificador del usuario, si está disponible
branch_idcadenaLa rama del agente utilizada para la conversación, si procede
version_idcadenaEl ID de la versión del agente (instantánea) activa durante la llamada
environmentcadenaEl entorno utilizado para resolver variables de entorno
transcriptmatrizTranscripción completa de la conversación con turnos
metadataobjetoTiempos de llamada, costes y datos telefónicos
analysisobjetoResultados de la evaluación y resumen de la conversación
conversation_initiation_client_dataobjetoAnulaciones de configuración y variables dinámicas
has_audiobooleanoSi hay audio disponible para la conversación
has_user_audiobooleanoSi hay audio del usuario disponible para la conversación
has_response_audiobooleanoSi hay audio de respuesta del agente disponible para la conversación

Webhooks de audio (post_call_audio)

Incluyen datos mínimos con el audio completo de la conversación como MP3 codificado en base64.

Campos de nivel superior

CampoTipoDescripción
typecadenaTipo de evento (siempre post_call_audio)
dataobjetoDatos mínimos de audio
event_timestampnúmeroCuándo ocurrió este evento en tiempo Unix UTC

Estructura del objeto de datos

El objeto data contiene únicamente:

CampoTipoDescripción
agent_idcadenaEl ID del agente que gestionó la llamada
conversation_idcadenaIdentificador único de la conversación
full_audiocadenaCadena codificada en base64 con el audio completo de la conversación en formato MP3

Los webhooks de audio solo contienen los tres campos indicados anteriormente. NO incluyen datos de transcripción, metadatos, resultados de análisis ni ningún otro detalle de la conversación.

Webhooks de error al iniciar la llamada (call_initiation_failure)

Incluyen información sobre intentos de iniciar llamadas telefónicas, como los motivos del error y los metadatos del proveedor de telefonía.

Los eventos del webhook de error al iniciar la llamada se envían cuando una llamada no puede iniciarse por errores de conexión, porque el usuario rechaza la llamada o porque no responde. Si una llamada va al buzón de voz o la responde un servicio automatizado, no se envía ningún webhook de error al iniciar la llamada, ya que la llamada se inició correctamente.

Campos de nivel superior

CampoTipoDescripción
typecadenaTipo de evento (siempre call_initiation_failure)
dataobjetoDatos de error al iniciar la llamada
event_timestampnúmeroCuándo ocurrió este evento en tiempo Unix UTC

Estructura del objeto de datos

El objeto data contiene:

CampoTipoDescripción
agent_idcadenaEl ID del agente asignado para gestionar la llamada
conversation_idcadenaIdentificador único de la conversación
failure_reasoncadenaEl motivo del error (“busy”, “no-answer”, “unknown”)
metadataobjetoDatos adicionales proporcionados por el proveedor de telefonía.

Estructura del objeto de metadatos

La estructura del objeto metadata varía según si la llamada saliente se realizó mediante Twilio o mediante trunking SIP. El objeto incluye un campo type que distingue entre ambos y un campo body con detalles específicos del proveedor.

Metadatos de SIP (type: "sip"):

CampoTipoObligatorioDescripción
typecadenaSíTipo de proveedor (siempre sip)
bodyobjetoSíInformación específica de SIP sobre el error de llamada

El objeto body de los metadatos de SIP contiene:

CampoTipoObligatorioDescripción
from_numbernúmeroSíEl número de teléfono de la parte que inició la llamada.
to_numbernúmeroSíEl número de teléfono de la parte llamada.
sip_status_codenúmeroSíCódigo de estado de respuesta SIP (p. ej., 486 para ocupado)
error_reasoncadenaSíDescripción del error legible para humanos
call_sidcadenaSíIdentificador de sesión de la llamada SIP
twirp_codecadenaNoCódigo de error de Twirp, si procede
sip_statuscadenaNoTexto de estado SIP correspondiente al código de estado

Metadatos de Twilio (type: "twilio"):

CampoTipoObligatorioDescripción
typecadenaSíTipo de proveedor (siempre twilio)
bodyobjetoSíCuerpo de StatusCallback de Twilio con detalles de la llamada, documentado aquí

Ejemplos de cargas útiles de webhook

Ejemplo de webhook de transcripción

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

Ejemplo de webhook de audio

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

Ejemplos de webhook de error al iniciar la llamada

Ejemplo de metadatos de Twilio

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

Ejemplo de metadatos de SIP

{
"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 audio

Los webhooks de audio se envían por separado de los webhooks de transcripción y solo contienen los campos esenciales para identificar la conversación, junto con los datos de audio codificados en base64.

Puedes activar o desactivar los webhooks de audio con el interruptor “Enviar datos de audio” en la configuración de webhooks. Puedes configurar este ajuste tanto a nivel de espacio de trabajo (en la configuración de ElevenAgents) como a nivel de agente (en las anulaciones de webhook de cada agente).

Entrega por streaming

Los webhooks de audio se envían como solicitudes HTTP en streaming con la cabecera transfer-encoding: chunked para gestionar archivos de audio grandes de forma eficiente.

Procesar webhooks de audio

Como los webhooks de audio se envían mediante codificación de transferencia por bloques, tendrás que gestionar correctamente los datos en streaming:

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

Los webhooks de audio pueden ser archivos grandes, así que asegúrate de que tu ruta de webhook pueda gestionar solicitudes en streaming y tenga suficiente capacidad de memoria y almacenamiento. El audio se envía en formato MP3.

Casos de uso

Seguimientos automatizados de llamadas

Los webhooks posteriores a la llamada te permiten crear workflows automatizados que se activan inmediatamente cuando termina una llamada. Estas son algunas aplicaciones prácticas:

Integración con CRM

Actualiza tu sistema de gestión de relaciones con clientes con los datos de la conversación en cuanto termine una llamada:

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

Conversaciones con estado

Mantén el contexto de la conversación entre varias interacciones almacenando y recuperando el estado:

  1. Cuando comience una llamada, incluye el ID de usuario como variable dinámica.
  2. Cuando termine una llamada, configura tu ruta de webhook para almacenar los datos de la conversación en tu base de datos a partir del ID de usuario extraído de dynamic_variables.
  3. Cuando el usuario vuelva a llamar, podrás recuperar este contexto y pasarlo a la nueva conversación mediante una variable dinámica {{previous_topics}}.
  4. Esto crea una experiencia fluida en la que el agente “recuerda” interacciones anteriores.
// 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(", "),
},
});
}