Webhooks après appel

Recevez une notification à la fin des appels et une fois l’analyse terminée via des webhooks.

Présentation

Les webhooks après appel vous permettent de recevoir des informations détaillées sur un appel une fois l’analyse terminée. Lorsqu’ils sont activés, ElevenLabs envoie une requête POST à l’endpoint que vous avez indiqué, avec des données complètes sur l’appel.

ElevenLabs prend en charge trois types de webhooks après appel :

  • Webhooks de transcription (post_call_transcription) : contiennent l’ensemble des données de la conversation, notamment les transcriptions, les résultats d’analyse et les métadonnées
  • Webhooks audio (post_call_audio) : contiennent un minimum de données avec l’audio encodé en base64 de l’intégralité de la conversation
  • Webhooks d’échec d’initiation d’appel (call_initiation_failure) : contiennent des informations sur les tentatives d’initiation d’appel échouées, notamment les raisons de l’échec et les métadonnées

Activer les webhooks après appel

Les webhooks après appel peuvent être activés pour tous les agents de votre Workspace via la page des paramètres d’ElevenAgents.

Paramètres des webhooks après appel

Les webhooks après appel doivent renvoyer un code d’état 200 pour être considérés comme réussis. Les webhooks qui échouent à plusieurs reprises sont désactivés automatiquement en cas de 10 échecs consécutifs ou plus, si la dernière livraison réussie date de plus de 7 jours ou si aucune livraison n’a jamais réussi.

Les webhooks après appel peuvent faire l’objet de nouvelles tentatives automatiques en cas d’échec. Consultez les nouvelles tentatives de webhook.

Authentification

Il est important que le récepteur valide tous les webhooks entrants. Les webhooks prennent actuellement en charge l’authentification par signatures HMAC. Configurez l’authentification HMAC en :

  • Stockant de manière sécurisée le secret partagé généré lors de la création du webhook
  • Vérifiant l’en-tête ElevenLabs-Signature dans votre endpoint à l’aide du SDK

Le SDK JavaScript expose constructEvent ; le SDK Python expose construct_event avec rawBody, sig_header et secret (ils ne s’appellent pas payload / signature en Python). Les deux vérifient la signature, valident l’horodatage et analysent la charge utile JSON.

Exemple de gestionnaire de webhook utilisant 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"}

Liste d’autorisation d’adresses IP

Pour renforcer la sécurité, vous pouvez ajouter les IP de sortie statiques d’ElevenLabs à votre liste d’autorisation. Consultez la liste d’autorisation d’adresses IP pour obtenir la liste complète des adresses IP.

L’utilisation d’une liste d’autorisation d’adresses IP avec la validation de signature HMAC assure plusieurs couches de sécurité.

Structure des réponses webhook

ElevenLabs envoie trois types distincts de webhooks post-appel, chacun avec une structure de données différente :

Webhooks de transcription (post_call_transcription)

Contient des données complètes sur la conversation, notamment les transcriptions intégrales, les résultats d’analyse et les métadonnées.

Champs de premier niveau

ChampTypeDescription
typechaîneType d’événement (toujours post_call_transcription)
dataobjetDonnées de conversation utilisant la structure ConversationHistoryCommonModel
event_timestampnombreDate de survenue de cet événement au format Unix UTC

Structure de l’objet de données

L’objet data contient :

ChampTypeDescription
agent_idchaîneID de l’agent ayant traité l’appel
agent_namechaîneNom de l’agent au moment de la conversation
conversation_idchaîneIdentifiant unique de la conversation
statuschaîneStatut de la conversation (par exemple, “done”)
user_idchaîneIdentifiant utilisateur, s’il est disponible
branch_idchaîneBranche d’agent utilisée pour la conversation, le cas échéant
version_idchaîneID de la version d’agent (instantané) active pendant l’appel
environmentchaîneEnvironnement utilisé pour résoudre les variables d’environnement
transcripttableauTranscription complète de la conversation avec les tours de parole
metadataobjetHorodatage de l’appel, coûts et informations téléphoniques
analysisobjetRésultats de l’évaluation et résumé de la conversation
conversation_initiation_client_dataobjetRemplacements de configuration et variables dynamiques
has_audiobooléenIndique si un audio est disponible pour la conversation
has_user_audiobooléenIndique si l’audio utilisateur est disponible pour la conversation
has_response_audiobooléenIndique si l’audio de réponse de l’agent est disponible pour la conversation

Webhooks audio (post_call_audio)

Contient des données minimales avec l’audio complet de la conversation au format MP3 encodé en base64.

Champs de premier niveau

ChampTypeDescription
typechaîneType d’événement (toujours post_call_audio)
dataobjetDonnées audio minimales
event_timestampnombreDate de survenue de cet événement au format Unix UTC

Structure de l’objet de données

L’objet data contient uniquement :

ChampTypeDescription
agent_idchaîneID de l’agent ayant traité l’appel
conversation_idchaîneIdentifiant unique de la conversation
full_audiochaîneChaîne encodée en base64 contenant l’audio complet de la conversation au format MP3

Les webhooks audio contiennent uniquement les trois champs répertoriés ci-dessus. Ils n’incluent PAS les données de transcription, les métadonnées, les résultats d’analyse ni aucun autre détail de la conversation.

Webhooks d’échec d’initiation d’appel (call_initiation_failure)

Contient des informations sur les tentatives d’initiation d’appels téléphoniques, notamment les motifs d’échec et les métadonnées du fournisseur de téléphonie.

Les événements webhook d’échec d’initiation d’appel sont envoyés lorsqu’un appel ne peut pas être initié en raison d’erreurs de connexion, du refus de l’appel par l’utilisateur ou de l’absence de réponse de l’utilisateur. Si un appel est transféré vers la messagerie vocale ou pris par un service automatisé, aucun webhook d’échec d’initiation d’appel n’est envoyé, car l’appel a été initié avec succès.

Champs de premier niveau

ChampTypeDescription
typechaîneType d’événement (toujours call_initiation_failure)
dataobjetDonnées d’échec d’initiation d’appel
event_timestampnombreDate de survenue de cet événement au format Unix UTC

Structure de l’objet de données

L’objet data contient :

ChampTypeDescription
agent_idchaîneID de l’agent assigné au traitement de l’appel
conversation_idchaîneIdentifiant unique de la conversation
failure_reasonchaîneMotif de l’échec (“busy”, “no-answer”, “unknown”)
metadataobjetDonnées supplémentaires fournies par le fournisseur de téléphonie.

Structure de l’objet de métadonnées

La structure de l’objet metadata varie selon que l’appel sortant a été effectué via Twilio ou via le trunking SIP. L’objet comprend un champ type qui distingue les deux, ainsi qu’un champ body contenant des informations propres au fournisseur.

Métadonnées SIP (type: "sip") :

ChampTypeObligatoireDescription
typechaîneOuiType de fournisseur (toujours sip)
bodyobjetOuiInformations d’échec d’appel propres à SIP

L’objet body des métadonnées SIP contient :

ChampTypeObligatoireDescription
from_numbernombreOuiNuméro de téléphone de la partie ayant initié l’appel.
to_numbernombreOuiNuméro de téléphone de la partie appelée.
sip_status_codenombreOuiCode d’état de réponse SIP (par exemple, 486 pour occupé)
error_reasonchaîneOuiDescription de l’erreur lisible par un humain
call_sidchaîneOuiIdentifiant de session de l’appel SIP
twirp_codechaîneNonCode d’erreur Twirp, le cas échéant
sip_statuschaîneNonTexte d’état SIP correspondant au code d’état

Métadonnées Twilio (type: "twilio") :

ChampTypeObligatoireDescription
typechaîneOuiType de fournisseur (toujours twilio)
bodyobjetOuiCorps StatusCallback de Twilio contenant les détails de l’appel, documenté ici

Exemples de charges utiles webhook

Exemple de webhook de transcription

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

Exemple de webhook audio

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

Exemples de webhooks d’échec d’initiation d’appel

Exemple de métadonnées 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"
}
}
}
}

Exemple de métadonnées 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"
}
}
}
}

Livraison des webhooks audio

Les webhooks audio sont envoyés séparément des webhooks de transcription et contiennent uniquement les champs essentiels permettant d’identifier la conversation, ainsi que les données audio encodées en base64.

Vous pouvez activer ou désactiver les webhooks audio à l’aide du bouton « Send audio data » dans les paramètres de votre webhook. Ce paramètre peut être configuré à la fois au niveau du Workspace (dans les paramètres d’ElevenAgents) et au niveau de l’agent (dans les remplacements de webhook propres à chaque agent).

Livraison en streaming

Les webhooks audio sont envoyés sous forme de requêtes HTTP en streaming avec l’en-tête transfer-encoding: chunked, afin de traiter efficacement les fichiers audio volumineux.

Traitement des webhooks audio

Les webhooks audio étant envoyés via un encodage de transfert par segments, vous devez gérer correctement les données 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

Les webhooks audio peuvent contenir des fichiers volumineux. Assurez-vous donc que votre endpoint webhook peut traiter les requêtes en streaming et dispose de capacités de mémoire et de stockage suffisantes. L’audio est envoyé au format MP3.

Cas d’utilisation

Suivis d’appel automatisés

Les webhooks post-appel vous permettent de créer des workflows automatisés qui se déclenchent immédiatement après la fin d’un appel. Voici quelques applications pratiques :

Intégration CRM

Mettez à jour votre système de gestion de la relation client avec les données de conversation dès la fin d’un appel :

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

Conversations avec état

Conservez le contexte de la conversation entre plusieurs interactions en stockant et en récupérant l’état :

  1. Au début d’un appel, transmettez votre ID utilisateur en tant que variable dynamique.
  2. À la fin d’un appel, configurez votre endpoint webhook pour stocker les données de conversation dans votre base de données, à partir de l’ID utilisateur extrait de dynamic_variables.
  3. Lorsque l’utilisateur appelle à nouveau, vous pouvez récupérer ce contexte et le transmettre à la nouvelle conversation dans une variable dynamique {{previous_topics}}.
  4. Vous créez ainsi une expérience fluide dans laquelle l’agent « se souvient » des interactions précédentes.
// 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(", "),
},
});
}