telephony.complete / web.complete
Bir çağrı sona erdiğinde transkript, kayıt URL
Her arama sona erdikten sonra bir tamamlama olayı tetiklenir: gelen telefon, giden telefon, web araması veya test araması (oluşturucu mikrofon oturumu). Bu olay engelleyici değildir: herhangi bir 2xx ile yanıt verin.
Olay her iki yola da iletilir:
- Webhook uç noktaları, aşağıda belgelenen kararlı yük,
her teslimat için bir
event_id, 30 sn zaman aşımı ve 24 saate kadar yeniden denemeler iletelephony.complete(telefon aramaları) veyaweb.complete(web aramaları ve oluşturucu mikrofon test aramaları) alır. - Eski tek URL webhook'u biraz farklı bir yükle tek bir eşzamanlı deneme alır (10 sn zaman aşımı, yeniden deneme yok) — bkz. Eski yük farkları.
İstek yükü (uç nokta teslimatları)
{
"data": {
"billable_minutes": 1.25,
"billing_total_cents": 8,
"call_id": 987654321,
"direction": "inbound",
"duration_seconds": 54,
"end_reason": "user_hangup",
"end_time": "2026-04-20T18:25:04.822Z",
"from_number": "+14155550199",
"product": "spark",
"recording_url": "https://storage.example.com/…",
"start_time": "2026-04-20T18:24:10.113Z",
"status": "completed",
"to_number": "+15551234567",
"transcripts": [ /* see Transcript format */ ],
"transfer_number": null,
"voice": "john"
},
"event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
"type": "telephony.complete"
}| Alan | Tür | Açıklama |
|---|---|---|
call_id | integer | Bu arama için her olayda sabittir |
direction | string | inbound, outbound, web, test. Geçmiş yükler eski mic veya widget değerlerini içerebilir |
from_number, to_number | string | E.164. from_number, web aramaları ve test aramaları için tam olarak "web" değeridir |
origin_domain | string | Yalnızca web/test — widget'ı barındıran sayfanın kaynağıdır (mikrofon oturumları için boştur) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | Başlangıç/bitiş zamanlarından türetilir |
status | string | completed veya failed |
end_reason | string | Aşağıdaki tabloya bakın |
product, voice | string | Arama sırasında etkin olan ajan yapılandırması |
transfer_number | string | null | Arama aktarıldığında ayarlanır |
recording_url | string | null | Süresi dolan imzalı URL; hemen indirin. Kayıt yapıtı mevcut olmadığında null olur |
billable_minutes | number | Faturalandırılan dakika sayısı, en yakın çeyrek dakikaya yuvarlanır (15 saniyelik artışlar, en az 0,25). Doğrudan sesli mesaja giden aramalar burada gerçek ölçülen dakikalarını yine bildirir, ancak ücret plan oranında bir dakikayla sınırlanır. |
billing_total_cents | integer | USD senti |
transcripts | array | Tur başına transkript girdileri; transkript kullanılamadığında boş olabilir |
Bitiş nedenleri
| Değer | Anlamı |
|---|---|
user_hangup | Uzak taraf önce kapattı |
ai_hangup | Yapay zeka aramayı kasıtlı olarak sonlandırdı |
ai_transfer | Yapay zeka aramayı aktardı; transfer_number ayarlanır |
ai_warm_transfer | Yapay zeka sıcak (katılımlı) aktarımı tamamladı |
voicemail_hangup | Sesli mesaj algılandı ve arama voicemail_action uyarınca sonlandırıldı |
max_duration | Arama maksimum süre sınırına ulaştı |
superseded | Oturum daha yeni bir oturumla değiştirildi |
unknown | Bitiş nedeni belirlenemedi |
Transkript biçimi
transcripts içindeki her kayıt, bir konuşma dönüşüdür. Roller şunlardır:
user (arayanın konuşması), model (ajan konuşması ve araç çağrıları),
tool (araç sonuçları) ve system (dil değişiklikleri gibi çağrı
olayları).
[
{
"role": "user",
"content_type": "text/plain",
"content": "Hi, I'm calling about my appointment.",
"start_ms": 1200,
"end_ms": 4100,
"audio_url": "https://storage.example.com/…"
},
{
"role": "model",
"content_type": "text/plain",
"content": "Sure, what date works best?",
"start_ms": 4200,
"end_ms": 6100
},
{
"role": "model",
"content_type": "application/json",
"content": {
"tool_call": "search_appointments",
"arguments": { "date": "2026-04-21" }
}
},
{
"role": "tool",
"content_type": "application/json",
"content": {
"tool_name": "search_appointments",
"response": { "available_slots": ["9:00 AM", "2:00 PM"] }
}
}
]| Alan | Tür | Açıklama |
|---|---|---|
role | dize | user, model, tool veya system |
content_type | dize | Konuşma için text/plain; araç çağrıları, araç sonuçları ve sistem olayları için application/json |
content | dize | nesne | Konuşma metni veya yukarıda gösterilen yapılandırılmış nesne. Araç çağrıları: {"tool_call": name, "arguments": {…}}. Araç sonuçları: {"tool_name": name, "response": {…}} |
start_ms, end_ms | tamsayı | Çağrı başlangıcından itibaren ofsetler, ms. Ses zamanlaması bilindiğinde bulunur |
ttfa_ms | tamsayı | Ölçüldüğünde, bir model dönüşü için ilk sese kadar geçen süre |
audio_url, audio_urls | dize / dizi | Dönüş bazında kaydedildiğinde, dönüş sesine ait süresi dolan imzalı URL'ler |
Tam yapılandırılmış dönüş geçmişi için (kesinti işaretleri,
onay istemleri ve ham konumlar dahil)
GET /v1/calls/{call_id}/history kullanın.
Eski yük farkları
Eski tek URL webhook zarfı
{"type": "telephony.complete" | "web.complete", "data": {…}} biçimindedir;
event_id içermez ve data alanı uç nokta yükünden farklıdır:
- Dönüş dizisi
transcriptsyerinehistoryaltındadır (yukarıdakiyle aynı dönüş şeması). - Alan kümesi, ham çağrı sonu raporudur ve yukarıdaki tablonun ötesinde ek dahili alanlar içerebilir — bilinmeyen alanları bilgilendirme amaçlı olarak değerlendirin.
- Web çağrıları (
direction: "web")from_number/to_numberalanlarını atlayarakorigin_domainekler. - Builder mikrofon test çağrıları eski yolda
telephony.completeolarak raporlanır (uç nokta sistemi bunlarıweb.completeolarak eşler). - Aktarım koordinasyonu: Bir çağrı aktarımla sona erdiğinde, eski webhook
eşzamanlı olarak çağrılır ve aktarım hedefinin hazır olmadığını belirtmek için
{"transfer_ready": false}yanıtını verebilir. Başka herhangi bir yanıt (veya eski webhook'un olmaması), aktarımın devam etmesini sağlar. Uç nokta teslimatlarına bunun için hiçbir zaman başvurulmaz.
Örnek işleyici
import hashlib
import hmac
import json
import os
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
def verify(body: bytes, signature: str) -> bool:
expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, signature or "")
@app.post("/thunderphone-webhook")
async def webhook(request: Request):
body = await request.body()
if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
raise HTTPException(status_code=401)
event = json.loads(body)
if event["type"] in ("telephony.complete", "web.complete"):
data = event["data"]
# Endpoint deliveries use "transcripts"; the legacy webhook uses "history".
turns = data.get("transcripts") or data.get("history") or []
await persist_call_record(
call_id=data["call_id"],
turns=turns,
recording_url=data.get("recording_url"),
)
if data["end_reason"] in ("ai_transfer", "ai_warm_transfer"):
await notify_team(data.get("transfer_number"), data["call_id"])
return {"ok": True}import crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
function verify(body, signature) {
const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
return signature &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
}
app.post(
"/thunderphone-webhook",
express.raw({ type: "application/json" }),
async (req, res) => {
if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
if (["telephony.complete", "web.complete"].includes(event.type)) {
const data = event.data;
// Endpoint deliveries use "transcripts"; the legacy webhook uses "history".
const turns = data.transcripts ?? data.history ?? [];
await persistCallRecord({ ...data, turns });
if (["ai_transfer", "ai_warm_transfer"].includes(data.end_reason)) {
await notifyTeam(data.transfer_number, data.call_id);
}
}
res.json({ ok: true });
},
);Yaygın kullanım alanları
Her aramanın transkriptini ve kayıt URL'sini müşteri kayıtlarınızla birlikte saklayın.
Konu modelleme, CSAT sinyali çıkarma veya aktarma oranı izleme için transkriptleri bir işleme hattına aktarın.
İnsan incelemesi için aramaları bir kalite güvence aracında açın veya kendi değerlendirme modelinizden geçirin.
Aktarma veya başarısızlık durumunda bir insan ekip arkadaşını tetikleyin.