통화 후 웹훅

통화가 종료되고 분석이 완료되면 웹훅으로 알림을 받으세요.

개요

통화 후 웹훅을 사용하면 분석이 완료된 후 통화에 관한 상세 정보를 받을 수 있습니다. 활성화하면 ElevenLabs가 포괄적인 통화 데이터와 함께 지정한 엔드포인트로 POST 요청을 보냅니다.

ElevenLabs는 세 가지 유형의 통화 후 웹훅을 지원합니다.

  • 트랜스크립션 웹훅 (post_call_transcription): 트랜스크립트, 분석 결과 및 메타데이터를 포함한 전체 대화 데이터
  • 오디오 웹훅 (post_call_audio): 전체 대화의 base64 인코딩 오디오가 포함된 최소 데이터
  • 통화 시작 실패 웹훅 (call_initiation_failure): 실패 사유 및 메타데이터를 포함한 통화 시작 실패 시도에 대한 정보

통화 후 웹훅 활성화

통화 후 웹훅은 ElevenAgents 설정 페이지를 통해 워크스페이스의 모든 에이전트에 대해 활성화할 수 있습니다.

통화 후 웹훅 설정

통화 후 웹훅이 성공한 것으로 간주되려면 200 상태 코드를 반환해야 합니다. 웹훅이 반복적으로 실패하고 연속 실패 횟수가 10회 이상이며 마지막 성공 전송이 7일 이상 전이거나 한 번도 성공적으로 전송된 적이 없는 경우 자동으로 비활성화됩니다.

통화 후 웹훅은 실패 시 자동으로 재시도될 수 있습니다. 웹훅 재시도를 참조하세요.

인증

수신 측에서는 들어오는 모든 웹훅의 유효성을 검증하는 것이 중요합니다. 현재 웹훅은 HMAC 서명을 통한 인증을 지원합니다. 다음 방법으로 HMAC 인증을 설정하세요.

  • 웹훅 생성 시 생성되는 공유 시크릿을 안전하게 저장합니다.
  • SDK를 사용하여 엔드포인트에서 ElevenLabs-Signature 헤더를 검증합니다.

JavaScript SDK는 constructEvent를 제공하며, Python SDK는 rawBody, sig_header, **secret**을 사용하는 construct_event를 제공합니다(Python에서는 payload / signature이라는 이름을 사용하지 않습니다). 두 SDK 모두 서명을 검증하고 타임스탬프를 확인하며 JSON 페이로드를 파싱합니다.

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

IP 허용 목록

보안을 강화하려면 ElevenLabs의 고정 송신 IP를 허용 목록에 추가할 수 있습니다. 전체 IP 주소 목록은 IP 허용 목록을 참조하세요.

IP 허용 목록을 HMAC 서명 검증과 함께 사용하면 여러 보안 계층을 제공할 수 있습니다.

웹훅 응답 구조

ElevenLabs는 서로 다른 데이터 구조를 가진 3가지 유형의 통화 후 웹훅을 전송합니다.

트랜스크립션 웹훅 (post_call_transcription)

전체 트랜스크립트, 분석 결과, 메타데이터를 포함한 포괄적인 대화 데이터를 담습니다.

최상위 필드

필드유형설명
type문자열이벤트 유형(항상 post_call_transcription)
data객체ConversationHistoryCommonModel 구조를 사용하는 대화 데이터
event_timestamp숫자이 이벤트가 발생한 UTC Unix 시간

데이터 객체 구조

data 객체에는 다음이 포함됩니다.

필드유형설명
agent_id문자열통화를 처리한 에이전트의 ID
agent_name문자열대화 당시 에이전트의 이름
conversation_id문자열대화의 고유 식별자
status문자열대화 상태(예: “done”)
user_id문자열사용 가능한 경우 사용자 식별자
branch_id문자열해당하는 경우 대화에 사용된 에이전트 브랜치
version_id문자열통화 중 활성화된 에이전트 버전(스냅샷)의 ID
environment문자열환경 변수를 확인하는 데 사용된 환경
transcript배열턴이 포함된 전체 대화 트랜스크립트
metadata객체통화 시간, 비용 및 전화 세부정보
analysis객체평가 결과 및 대화 요약
conversation_initiation_client_data객체구성 재정의 및 동적 변수
has_audio불리언대화에 사용 가능한 오디오가 있는지 여부
has_user_audio불리언대화에 사용자 오디오를 사용할 수 있는지 여부
has_response_audio불리언대화에 에이전트 응답 오디오를 사용할 수 있는지 여부

오디오 웹훅 (post_call_audio)

Base64로 인코딩된 MP3 형식의 전체 대화 오디오와 최소한의 데이터를 담습니다.

최상위 필드

필드유형설명
type문자열이벤트 유형(항상 post_call_audio)
data객체최소 오디오 데이터
event_timestamp숫자이 이벤트가 발생한 UTC Unix 시간

데이터 객체 구조

data 객체에는 다음만 포함됩니다.

필드유형설명
agent_id문자열통화를 처리한 에이전트의 ID
conversation_id문자열대화의 고유 식별자
full_audio문자열MP3 형식의 전체 대화 오디오를 포함하는 Base64 인코딩 문자열

오디오 웹훅에는 위에 나열된 3개 필드만 포함됩니다. 트랜스크립트 데이터, 메타데이터, 분석 결과 또는 기타 대화 세부정보는 포함되지 않습니다.

통화 시작 실패 웹훅 (call_initiation_failure)

실패 사유와 전화 통신 제공업체 메타데이터를 포함하여 전화 통화 시작 시도에 관한 정보를 담습니다.

통화 시작 실패 웹훅 이벤트는 연결 오류, 사용자의 통화 거절 또는 사용자의 미응답으로 인해 통화를 시작하지 못했을 때 전송됩니다. 통화가 음성 사서함으로 연결되거나 자동화된 서비스가 받는 경우에는 통화가 성공적으로 시작된 것이므로 통화 시작 실패 웹훅이 전송되지 않습니다.

최상위 필드

필드유형설명
type문자열이벤트 유형(항상 call_initiation_failure)
data객체통화 시작 실패 데이터
event_timestamp숫자이 이벤트가 발생한 UTC Unix 시간

데이터 객체 구조

data 객체에는 다음이 포함됩니다.

필드유형설명
agent_id문자열통화 처리를 담당하도록 배정된 에이전트의 ID
conversation_id문자열대화의 고유 식별자
failure_reason문자열실패 사유(“busy”, “no-answer”, “unknown”)
metadata객체전화 통신 제공업체가 제공하는 추가 데이터입니다.

메타데이터 객체 구조

metadata 객체 구조는 발신 통화가 Twilio를 통해 이루어졌는지, SIP 트렁킹을 통해 이루어졌는지에 따라 다릅니다. 이 객체에는 두 방식을 구분하는 type 필드와 제공업체별 세부정보를 담은 body 필드가 포함됩니다.

SIP 메타데이터 (type: "sip"):

필드유형필수 여부설명
type문자열예제공업체 유형(항상 sip)
body객체예SIP별 통화 실패 정보

SIP 메타데이터의 body 객체에는 다음이 포함됩니다.

필드유형필수 여부설명
from_number숫자예통화를 시작한 상대방의 전화번호입니다.
to_number숫자예전화를 받은 상대방의 전화번호입니다.
sip_status_code숫자예SIP 응답 상태 코드(예: 통화 중인 경우 486)
error_reason문자열예사람이 읽을 수 있는 오류 설명
call_sid문자열예SIP 통화 세션 식별자
twirp_code문자열아니요해당하는 경우 Twirp 오류 코드
sip_status문자열아니요상태 코드에 해당하는 SIP 상태 텍스트

Twilio 메타데이터 (type: "twilio"):

필드유형필수 여부설명
type문자열예제공업체 유형(항상 twilio)
body객체예통화 세부정보를 포함하는 Twilio StatusCallback 본문으로, 여기에 문서화되어 있습니다

웹훅 페이로드 예시

트랜스크립션 웹훅 예시

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

오디오 웹훅 예시

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

통화 시작 실패 웹훅 예시

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

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

오디오 웹훅 전송

오디오 웹훅은 트랜스크립션 웹훅과 별도로 전송되며, 대화를 식별하는 데 필요한 필수 필드와 Base64 인코딩 오디오 데이터만 포함합니다.

웹훅 설정의 “오디오 데이터 전송” 토글을 사용해 오디오 웹훅을 활성화하거나 비활성화할 수 있습니다. 이 설정은 워크스페이스 수준(ElevenAgents 설정)과 에이전트 수준(개별 에이전트 웹훅 재정의)에서 모두 구성할 수 있습니다.

스트리밍 전송

오디오 웹훅은 대용량 오디오 파일을 효율적으로 처리하기 위해 transfer-encoding: chunked 헤더를 포함한 스트리밍 HTTP 요청으로 전송됩니다. 각 요청은 5분 후 시간 초과됩니다.

재시도

웹훅에서 재시도가 활성화된 경우, 실패한 오디오 전송은 트랜스크립션 웹훅과 동일한 일정으로 재시도됩니다. 재시도 시 전체 오디오 페이로드가 다시 전송되므로 conversation_id를 기준으로 중복을 제거하세요. 일정, 재시도 가능한 오류 및 오디오 크기 제한은 웹훅 재시도를 참조하세요.

오디오 웹훅 처리

오디오 웹훅은 청크 전송 인코딩을 통해 전송되므로 스트리밍 데이터를 올바르게 처리해야 합니다.

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

오디오 웹훅은 대용량 파일일 수 있으므로 웹훅 엔드포인트가 스트리밍 요청을 처리할 수 있고 충분한 메모리 및 스토리지 용량을 갖추었는지 확인하세요. 오디오는 MP3 형식으로 전송됩니다.

사용 사례

자동화된 통화 후 후속 작업

통화 후 웹훅을 사용하면 통화가 끝난 직후 트리거되는 자동화된 workflow를 구축할 수 있습니다. 다음은 몇 가지 실용적인 활용 사례입니다.

CRM 통합

통화가 완료되는 즉시 고객 관계 관리 시스템을 대화 데이터로 업데이트합니다.

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

상태 유지 대화

상태를 저장하고 불러와 여러 상호작용에 걸쳐 대화 컨텍스트를 유지합니다.

  1. 통화가 시작되면 사용자 ID를 동적 변수로 전달합니다.
  2. 통화가 끝나면 dynamic_variables에서 추출한 사용자 ID를 기준으로 웹훅 엔드포인트가 데이터베이스에 대화 데이터를 저장하도록 설정합니다.
  3. 사용자가 다시 전화하면 이 컨텍스트를 불러와 새 대화의 {{previous_topics}} 동적 변수로 전달할 수 있습니다.
  4. 이를 통해 에이전트가 이전 상호작용을 “기억”하는 원활한 경험을 만들 수 있습니다.
// 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(", "),
},
});
}