ナビゲーションにスキップ

通話後Webhook

通話が終了し、分析が完了した際にWebhookで通知を受け取ります。

概要

通話後のWebhookを使用すると、分析完了後に通話に関する詳細情報を受け取れます。有効にすると、ElevenLabsは包括的な通話データを含むPOSTリクエストを指定したエンドポイントに送信します。

ElevenLabsは、次の3種類の通話後Webhookをサポートしています。

  • 文字起こしWebhook(post_call_transcription):文字起こし、分析結果、メタデータを含む完全な会話データ
  • 音声Webhook(post_call_audio):会話全体のBase64エンコード済み音声を含む最小限のデータ
  • 通話開始失敗Webhook(call_initiation_failure):失敗理由やメタデータを含む、通話開始に失敗した試行に関する情報

通話後Webhookを有効にする

通話後Webhookは、ElevenAgentsの設定ページからワークスペース内のすべてのエージェントに対して有効にできます。

通話後Webhookの設定

通話後Webhookが成功とみなされるには、200ステータスコードを返す必要があります。Webhookは、10回以上連続して失敗し、最後の 配信成功から7日以上経過しているか、一度も正常に配信されていない場合、自動的に無効になります。

通話後Webhookは、失敗した場合に自動で再試行できます。詳しくはWebhookの 再試行を参照してください。

認証

受信側は、すべての受信Webhookを検証することが重要です。Webhookは現在、HMAC署名による認証に対応しています。HMAC認証は次の手順で設定します。

  • Webhookの作成時に生成された共有シークレットを安全に保存する
  • SDKを使用してエンドポイントでElevenLabs-Signatureヘッダーを検証する

JavaScript SDKではconstructEventを、Python SDKでは**rawBody、sig_header、secret**を指定するconstruct_eventを利用できます(Pythonではpayload/signatureという名前ではありません)。どちらも署名の検証、タイムスタンプの検証、JSONペイロードの解析を行います。

FastAPIを使用したWebhookハンドラーの例:

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

カスタムリクエストヘッダー

HMAC署名検証に加えて、Webhookの配信ごとにカスタムヘッダーを含めることができます。これは、受信エンドポイントで必要となるAPIキー、Bearerトークン、その他の認証ヘッダーを渡す場合に便利です。

ElevenAgentsダッシュボードでは、現在カスタムヘッダーを設定できません。SDKまたはAPIを使用してカスタムヘッダーを設定してください。

カスタムヘッダーはHMAC認証と併用できます。auth_typeは引き続きhmacに設定する必要があり、 カスタムヘッダーはElevenLabs-Signatureヘッダーに加えて送信されます。

カスタムヘッダーは書き込み専用です。Webhook一覧エンドポイントの レスポンスにはrequest_headersが返されないため、Webhookに設定されているヘッダーを 読み戻すことはできません。

IP許可リスト

セキュリティを強化するため、ElevenLabsの静的エグレスIPを許可リストに追加できます。IPアドレスの完全な一覧は、IP許可リストを参照してください。

IP許可リストをHMAC署名検証と組み合わせることで、複数層のセキュリティを提供できます。

エージェントごとのWebhookオーバーライド

デフォルトでは、ワークスペース内のすべてのエージェントが、ワークスペース設定で構成されたWebhookを使用します。エージェントごとにこれをオーバーライドし、異なるエージェントの通話後データを異なるエンドポイントへ送信できます。

1

エージェント設定を開く

ElevenAgentsダッシュボードでエージェントを開き、設定に移動します。

2

Webhookオーバーライドを設定する

通話後Webhookセクションで、ドロップダウンから別のWebhookを選択して、このエージェントのワークスペースデフォルトをオーバーライドします。送信するイベントタイプも設定できます。

エージェントWebhookオーバーライドの設定

post_call_webhook_idは、ワークスペース内にすでに存在するWebhookを参照する必要があります。events配列を使用して、送信するイベントタイプを指定することもできます。

イベントタイプ説明
transcript会話の詳細と分析を含む文字起こしデータ
audio会話全体のBase64エンコード済みMP3音声
call_initiation_failure通話開始に失敗した試行に関する情報
answering_machine_detection留守番電話検出の結果(発信通話のみ)
unredacted_transcriptPIIを保持した文字起こし(適切なデータ保持設定が必要)
unredacted_audioPIIをマスキングしていない音声(適切なデータ保持設定が必要)

Webhookレスポンスの構造

ElevenLabsは、データ構造がそれぞれ異なる3種類の通話後Webhookを送信します。

文字起こしWebhook(post_call_transcription)

完全な文字起こし、分析結果、メタデータを含む包括的な会話データを提供します。

トップレベルフィールド

フィールド型説明
typestringイベントのタイプ(常にpost_call_transcription)
dataobjectConversationHistoryCommonModel構造を使用した会話データ
event_timestampnumberこのイベントが発生したUTCのUnix時刻

データオブジェクトの構造

dataオブジェクトには次が含まれます。

フィールド型説明
agent_idstring通話を処理したエージェントのID
agent_namestring会話時点でのエージェント名
conversation_idstring会話の一意の識別子
statusstring会話のステータス(例:“done”)
user_idstring利用可能な場合のユーザー識別子
branch_idstring該当する場合、会話に使用されたエージェントブランチ
version_idstring通話中に有効だったエージェントバージョン(スナップショット)のID
environmentstring環境変数の解決に使用された環境
transcriptarrayターンを含む完全な会話の文字起こし
metadataobject通話時間、コスト、電話の詳細
analysisobject評価結果と会話の要約
conversation_initiation_client_dataobject設定のオーバーライドと動的変数
has_audioboolean会話で利用可能な音声があるかどうか
has_user_audioboolean会話でユーザー音声を利用できるかどうか
has_response_audioboolean会話でエージェント応答音声を利用できるかどうか

音声Webhook(post_call_audio)

会話全体のBase64エンコード済みMP3音声を含む最小限のデータを提供します。

トップレベルフィールド

フィールド型説明
typestringイベントのタイプ(常にpost_call_audio)
dataobject最小限の音声データ
event_timestampnumberこのイベントが発生したUTCのUnix時刻

データオブジェクトの構造

dataオブジェクトには、次のみが含まれます。

フィールド型説明
agent_idstring通話を処理したエージェントのID
conversation_idstring会話の一意の識別子
full_audiostringMP3形式の完全な会話音声を含むBase64エンコード済み文字列

音声Webhookには、上記の3つのフィールドのみが含まれます。文字起こしデータ、メタデータ、 分析結果、その他の会話の詳細は含まれません。

通話開始失敗Webhook(call_initiation_failure)

失敗理由や電話プロバイダーのメタデータなど、電話通話の開始試行に関する情報を提供します。

通話開始失敗Webhookイベントは、接続エラー、ユーザーによる通話拒否、またはユーザーが応答しなかったことにより 通話の開始に失敗した場合に送信されます。通話が留守番電話につながった場合や自動サービスが応答した場合、通話は 正常に開始されているため、通話開始失敗Webhookは送信されません。

トップレベルフィールド

フィールド型説明
typestringイベントのタイプ(常にcall_initiation_failure)
dataobject通話開始失敗データ
event_timestampnumberこのイベントが発生したUTCのUnix時刻

データオブジェクトの構造

dataオブジェクトには次が含まれます。

フィールド型説明
agent_idstring通話の処理に割り当てられたエージェントのID
conversation_idstring会話の一意の識別子
failure_reasonstring失敗理由(“busy”、“no-answer”、“unknown”)
metadataobject電話プロバイダーから提供される追加データ

Twilio経由の発信通話では、failure_reasonはTwilioのCallStatusから導出されます。busyとno-answerはそのまま渡され、failedはunknownとして報告されます。その他のTwilio通話ステータスでは、通話開始失敗Webhookは送信されません。

メタデータオブジェクトの構造

metadataオブジェクトの構造は、発信通話がTwilio経由かSIPトランキング経由かによって異なります。このオブジェクトには、両者を区別するtypeフィールドと、プロバイダー固有の詳細を含むbodyフィールドがあります。

SIPメタデータ(type: "sip"):

フィールド型必須説明
typestringはいプロバイダータイプ(常にsip)
bodyobjectはいSIP固有の通話失敗情報

SIPメタデータのbodyオブジェクトには次が含まれます。

フィールド型必須説明
from_numbernumberはい通話を開始した相手の電話番号
to_numbernumberはい着信側の電話番号
sip_status_codenumberはいSIP応答ステータスコード(例:話中の場合は486)
error_reasonstringはい人が読めるエラーの説明
call_sidstringはいSIP通話セッション識別子
twirp_codestringいいえ該当する場合はTwirpエラーコード
sip_statusstringいいえステータスコードに対応するSIPステータステキスト

Twilioメタデータ(type: "twilio"):

フィールド型必須説明
typestringはいプロバイダータイプ(常にtwilio)
bodyobjectはい通話の詳細を含むTwilio StatusCallbackのbody。詳細はこちら

Webhookペイロードの例

文字起こしWebhookの例

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

音声Webhookの例

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

通話開始失敗Webhookの例

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

音声Webhookの配信

音声Webhookは文字起こしWebhookとは別に配信され、会話を識別するために必要な基本フィールドと、Base64エンコード済み音声データのみを含みます。

音声Webhookは、Webhook設定の「音声データを送信」トグルで有効または無効にできます。この設定は、 ワークスペースレベル(ElevenAgents設定内)とエージェントレベル(個別のエージェントWebhookオーバーライド内)の両方で 構成できます。

ストリーミング配信

音声Webhookは、大きな音声ファイルを効率的に処理するため、transfer-encoding: chunkedヘッダーを使用するストリーミングHTTPリクエストとして配信されます。各リクエストは5分後にタイムアウトします。

再試行

Webhookで再試行が有効になっている場合、失敗した音声配信は文字起こしWebhookと同じスケジュールで再試行されます。再試行では完全な音声ペイロードが再送されるため、conversation_idで重複を排除してください。スケジュール、再試行可能なエラー、音声サイズ制限については、Webhookの再試行を参照してください。

音声Webhookを処理する

音声Webhookはチャンク転送エンコーディングで配信されるため、ストリーミングデータを適切に処理する必要があります。

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

音声Webhookは大きなファイルになる場合があるため、Webhookエンドポイントがストリーミングリクエストを処理でき、 十分なメモリ/ストレージ容量を持つことを確認してください。音声はMP3形式で配信されます。

ユースケース

自動通話フォローアップ

通話後Webhookを使うと、通話終了直後にトリガーされる自動ワークフローを構築できます。実用的な活用例をいくつか紹介します。

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に基づいて会話データをデータベースに保存するよう、Webhookエンドポイントを設定します。
  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(", "),
},
});
}