The call.complete webhook fires when a call ends. Use it to receive transcripts, recording URLs, and call metadata for logging, analytics, or follow-up actions.
Webhook Payload
{
"type" : "call.complete" ,
"data" : {
"call_id" : 987654321 ,
"from_number" : "+15555550100" ,
"to_number" : "+15555550200" ,
"end_reason" : "ai_hangup" ,
"transcripts" : [ ... ],
"transfer_number" : null ,
"recording_url" : "https://storage.googleapis.com/..."
}
}
Field Type Description call_idinteger Unique call identifier from_numberstring Caller’s phone number to_numberstring Recipient’s phone number end_reasonstring Why the call ended transcriptsarray Full conversation transcript transfer_numberstring | null Number transferred to (if applicable) recording_urlstring URL to call recording
End Reasons
Reason Description ai_hangupAI ended the call user_hangupCaller hung up transferCall was transferred errorCall failed due to error timeoutCall timed out
The transcripts array contains all conversation turns with timestamps:
{
"transcripts" : [
{
"role" : "user" ,
"content_type" : "text/plain" ,
"content" : "Hi, I'm calling about my appointment." ,
"start_ms" : 1200 ,
"end_ms" : 4100
},
{
"role" : "assistant" ,
"content_type" : "text/plain" ,
"content" : "Sure, I'd be happy to help. What date works best?" ,
"start_ms" : 4200 ,
"end_ms" : 6100
},
{
"role" : "tool_call" ,
"content_type" : "application/json" ,
"content" : {
"name" : "search_appointments" ,
"arguments" : { "date" : "2025-01-02" }
},
"start_ms" : 6200 ,
"end_ms" : 6200
},
{
"role" : "tool_response" ,
"content_type" : "application/json" ,
"content" : {
"available_slots" : [ "9:00 AM" , "2:00 PM" , "4:30 PM" ]
},
"start_ms" : 6210 ,
"end_ms" : 6210
}
]
}
Field Type Description rolestring user, assistant, tool_call, or tool_responsecontent_typestring text/plain for speech, application/json for toolscontentstring | object Transcript text or JSON payload start_msinteger Start timestamp (ms, aligned to recording) end_msinteger End timestamp (ms, aligned to recording)
Tool calls and responses may be instantaneous (start_ms == end_ms).
Example Handler
from fastapi import FastAPI, Request
import json
app = FastAPI()
@app.post ( "/thunderphone-webhook" )
async def webhook ( request : Request):
event = await request.json()
if event[ "type" ] == "call.complete" :
data = event[ "data" ]
# Log call summary
print ( f "Call { data[ 'call_id' ] } ended: { data[ 'end_reason' ] } " )
# Extract user messages for analysis
user_messages = [
t[ "content" ]
for t in data[ "transcripts" ]
if t[ "role" ] == "user"
]
# Save to database
await save_call_record(
call_id = data[ "call_id" ],
from_number = data[ "from_number" ],
transcripts = data[ "transcripts" ],
recording_url = data[ "recording_url" ]
)
# Trigger follow-up actions
if data[ "end_reason" ] == "transfer" :
await notify_team(data[ "transfer_number" ], data[ "call_id" ])
return { "status" : "ok" }
Common Use Cases
CRM Integration Save call transcripts and recordings to your CRM for customer history.
Analytics Analyze transcripts to extract insights about customer needs.
Follow-up Actions Trigger notifications or emails based on call outcomes.
Quality Assurance Review transcripts and recordings for agent training.