Open in
telephony.complete / web.complete
Bir çağrı sona erdiğinde, transkript, kayıt URL
Her çağrı sona erdikten sonra bir tamamlama olayı tetiklenir — gelen telefon, giden telefon, web çağrısı veya test çağrısı (oluşturucu mikrofon oturumu). Bu olay engelleyici değildir: herhangi bir 2xx ile yanıt verin.
Olay her iki yola da teslim edilir:
- 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 çağrıları) veyaweb.complete(web çağrıları ve oluşturucu mikrofon test çağrıları) 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 farklılıkları.
İ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",
"extracted_data": {
"status": "completed",
"fields": {
"customer_name": "Alex Morgan",
"appointment_date": "2026-04-23"
},
"evidence": {
"customer_name": {
"quote": "My name is Alex Morgan",
"speaker_role": "caller",
"turn_index": 4
},
"appointment_date": {
"quote": "April 23 works for me",
"speaker_role": "caller",
"turn_index": 7
}
},
"verification": "verified",
"field_reasons": {},
"schema_version": "92850758e231a3c95a..."
},
"from_number": "+14155550199",
"product": "spark",
"recording_stopped_at": null,
"recording_stopped_reason": null,
"recording_url": "https://storage.example.com/…",
"start_time": "2026-04-20T18:24:10.113Z",
"status": "completed",
"summary": "Alex Morgan called to book a follow-up visit and confirmed Thursday, April 23. The agent read the appointment back and Alex asked for a reminder text the day before.",
"to_number": "+15551234567",
"transcripts": [ /* see Transcript format */ ],
"transfer_number": null,
"unresolved_variables": ["campaign_owner"],
"variables": {"campaign_name": "Spring renewals"},
"voice": "john"
},
"event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
"type": "telephony.complete"
}| Alan | Tür | Açıklama |
|---|---|---|
call_id | integer | Bu çağrı için her etkinlikte sabittir |
agent_id | integer | null | Atanmışsa, çağrıyı yöneten yapay zeka ajanı |
agent_name | string | null | Atanmışsa, çağrıyı yöneten yapay zeka ajanı |
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 çağrıları ve test çağrıları için tam olarak "web" değeridir |
origin_domain | string | Yalnızca web/test — widget'ı barındıran sayfanın kaynağı (mikrofon oturumları için boştur) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | Başlangıç/bitiş zamanından türetilir |
status | string | completed veya failed |
end_reason | string | Aşağıdaki tabloya bakın |
product, voice | string | Çağrı sırasında geçerli olan ajan yapılandırması |
variables | object | Çağrı başladığında anlık görüntüsü alınan giriş değişkenleri |
unresolved_variables | array | Çağrı yapılandırmasının başvurduğu ancak çağrı başlangıcında sağlanmayan değişken adları |
transfer_number | string | null | Çağrı aktarıldığında ayarlanır |
recording_url | string | null | Süresi dolan imzalı URL; hemen indirin. Kayıt yapıtı mevcut olmadığında null |
recording_stopped_at | timestamp | null | Kayıt, çağrı bitmeden önce durdurulduysa bu zamanı gösterir; bu andan sonraki hiçbir şey kayıtta yer almaz. Kayıt çağrının sonuna kadar sürdüyse veya çağrı kaydedilmediyse null |
recording_stopped_reason | string | null | Kaydın neden erken durduğunu belirtir. warm_transfer: bir sıcak aktarım hedefi aramadan hemen önce durur ve aktarım başarısız olup arayan ajana geri dönse bile kapalı kalır. Diğer değerleri bilgilendirme amaçlı kabul edin; daha fazlası eklenebilir. recording_stopped_at değeri null olduğunda null |
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 çağrılar burada gerçek ölçülen dakikalarını yine bildirir, ancak ücret plan tarifesi üzerinden bir dakikayla sınırlandırılır. |
billing_total_cents | integer | USD sent |
transcripts | array | Tur başına transkript girdileri; transkript mevcut olmadığında boş olabilir |
summary | string | null | Ajan oluşturucuda Toplanacak veriler altında Çağrı özeti oluştur seçeneği etkinleştirildiğinde ( Ajan üzerindeki call_summary_enabled) ve oluşturma başarılı olduğunda, çağrı sonrası sade dilli özet (iki ila dört düzyazı cümlesi, en fazla 2.000 karakter, çağrının dilinde yazılır: çağrı bunu geçersiz kılmadıysa ajanın birincil dilinde); aksi durumda null. Buna özetlenemeyecek kadar kısa çağrılar, transkripti olmayan çağrılar ve kaydı kapalı ajanlar da dahildir. Özet, çıkarma işlemiyle aynı çağrı sonrası adımda oluşturulur; bu nedenle Çağrı tamamlandı webhook'unu göndermeden önce çıkarılan verileri bekle ayarı (extraction_blocks_completion), bu etkinliğin özeti bekleyip beklemeyeceğini belirler: bekleme modunda bu etkinlik gönderilmeden önce özet hazırdır (özetin kendisi adımın 75 saniyelik bütçesi içinde en fazla 30 saniye alır); etkinlik beklemediğinde (never veya except_transfers kapsamındaki bir aktarım) bu alan null olur ve özet call.data_extracted içinde yeniden gönderilmez; bu nedenle daha sonra Çağrı nesnesi üzerindeki summary alanından okuyun. Ayrıca kontrol panelindeki çağrı sayfasında da kullanılabilir |
extracted_data | object | null | status, fields, evidence, verification, field_reasons ve schema_version içeren yapılandırılmış çıkarma sonucu. Null olmayan her alan, tam yapısal olarak kontrol edilmiş alıntıyı (en fazla 1.000 karakter) ve ayrıca konuşmacı rolü ile tur dizinini içerir; modelin döndürdüğü daha uzun alıntılar kısaltılmak yerine reddedilir. Alan null olduğunda kanıt null olur. verified, her adayın tam olarak bir geçerli bağımsız karar aldığı anlamına gelir. unavailable, hatalı biçimlendirilmiş veya kısmi doğrulayıcı çıktısını da kapsar; geçerli kısmi kararlar yine uygulanırken, geçerli bir kararı olmayan adaylar null olarak ayarlanır. status, completed, failed, exhausted, skipped veya skipped_recording_disabled değeridir; ajanın çıkarma alanı olmadığında null |
steps_trace | array | Yalnızca yapay zeka ajanında adımlar veya aktarımlar bulunan çağrılarda yer alır. Çağrının bunlar arasında ilerleme sırası: her girdi t_ms (çağrı başlangıcından itibaren milisaniye), agent (ajanın anahtarı, kimliğiyle birlikte a), step (adım kimliği veya adımı olmayan bir ajana aktarımda null) ve via (start, model, condition, on_enter veya handoff) içerir. Yalnızca kimlikleri ve zamanları tutar; bu nedenle kayıt kapalıyken de saklanır |
collected_values | object | Yalnızca yapay zeka ajanında adımlar veya aktarımlar bulunan çağrılarda yer alır. Ajanın adımlarında topladığı, toplama alanı adına göre anahtarlanmış değerler ile her adımın on_enter_tool sonucu için <tool>_result değerini içerir. Çağrı içeriği saklanmadığında (kayıt kapalıyken) dahil edilmez |
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 ayarlandı |
ai_warm_transfer | Yapay zeka sıcak (katılımlı) aktarımı tamamladı |
voicemail_hangup | Sesli mesaj algılandı ve arama voicemail_action ayarınıza göre sonlandırıldı |
max_duration | Arama maksimum süre sınırına ulaştı: ajanın veya aramanın kendi sınırı ya da 6 saatlik platform sınırı. Süre sınırı uyarısından sonra yapay zeka veya arayan tarafından sonlandırılan bir arama ai_hangup ya da user_hangup değerini korur |
superseded | Oturum daha yeni bir oturumla değiştirildi |
inbound_config_webhook_failed | Webhook modundaki bir numaraya gelen aramada yapılandırma webhook'u kullanılabilir bir yapılandırma döndürmedi; bu nedenle arama bir ajan yanıtlamadan önce sonlandırıldı. status değeri failed olur ve arama ücretlendirilmez |
unknown | Sonlandırma nedeni belirlenemedi |
Transkript biçimi
transcripts içindeki her giriş bir konuşma turudur. Roller
user (arayanın konuşması), model (ajanın konuşması ve araç çağrıları),
tool (araç sonuçları) ve system (dil değişimleri gibi çağrı
olayları) şeklindedir.
[
{
"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 | tam sayı | Çağrı başlangıcından itibaren milisaniye cinsinden ofsetler. Ses zamanlaması bilindiğinde bulunur |
ttfa_ms | tam sayı | Ölçüldüğünde, bir model turu için ilk sese kadar geçen süre |
audio_url, audio_urls | dize / dizi | Tur bazında kaydedildiğinde, turun sesi için süresi dolan imzalı URL'ler |
Tam yapılandırılmış tur 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'li 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:
Eski tamamlama yükü ayrıca agent_id ve agent_name taşır.
- Tur dizisi
transcriptsyerinehistoryaltında bulunur (yukarıdakiyle aynı tur şeması). - Alan kümesi ham çağrı sonu raporudur ve yukarıdaki tablonun ötesinde ek dahili alanlar içerebilir — bilinmeyen alanları bilgilendirici 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. Diğer tüm yanıtlar (veya eski webhook'un hiç 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 aktarım oranı izleme için transkriptleri bir işlem hattına aktarın.
İnsan incelemesi için aramaları bir QA aracında açın veya kendi değerlendirme modelinizden geçirin.
Aktarım / başarısızlık durumunda insan ekip arkadaşınızı tetikleyin.