ThunderPhone 2.0 yayında.Hemen kendiniz kullanmaya başlayın; dakikası 2 sentten başlayan fiyatlarla.Duyuruyu okuyun

Webhooks

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:

İ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"
}
AlanTürAçıklama
call_idintegerBu çağrı için her etkinlikte sabittir
agent_idinteger | nullAtanmışsa, çağrıyı yöneten yapay zeka ajanı
agent_namestring | nullAtanmışsa, çağrıyı yöneten yapay zeka ajanı
directionstringinbound, outbound, web, test. Geçmiş yükler eski mic veya widget değerlerini içerebilir
from_number, to_numberstringE.164. from_number, web çağrıları ve test çağrıları için tam olarak "web" değeridir
origin_domainstringYalnızca web/test — widget'ı barındıran sayfanın kaynağı (mikrofon oturumları için boştur)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullBaşlangıç/bitiş zamanından türetilir
statusstringcompleted veya failed
end_reasonstringAşağıdaki tabloya bakın
product, voicestringÇağrı sırasında geçerli olan ajan yapılandırması
variablesobjectÇağrı başladığında anlık görüntüsü alınan giriş değişkenleri
unresolved_variablesarrayÇağrı yapılandırmasının başvurduğu ancak çağrı başlangıcında sağlanmayan değişken adları
transfer_numberstring | nullÇağrı aktarıldığında ayarlanır
recording_urlstring | nullSüresi dolan imzalı URL; hemen indirin. Kayıt yapıtı mevcut olmadığında null
recording_stopped_attimestamp | nullKayı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_reasonstring | nullKaydı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_minutesnumberFaturalandı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_centsintegerUSD sent
transcriptsarrayTur başına transkript girdileri; transkript mevcut olmadığında boş olabilir
summarystring | nullAjan 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_dataobject | nullstatus, 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_tracearrayYalnı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_valuesobjectYalnı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ğerAnlamı
user_hangupUzak taraf önce kapattı
ai_hangupYapay zeka aramayı kasıtlı olarak sonlandırdı
ai_transferYapay zeka aramayı aktardı; transfer_number ayarlandı
ai_warm_transferYapay zeka sıcak (katılımlı) aktarımı tamamladı
voicemail_hangupSesli mesaj algılandı ve arama voicemail_action ayarınıza göre sonlandırıldı
max_durationArama 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
supersededOturum daha yeni bir oturumla değiştirildi
inbound_config_webhook_failedWebhook 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
unknownSonlandı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"] }
    }
  }
]
AlanTürAçıklama
roledizeuser, model, tool veya system
content_typedizeKonuşma için text/plain; araç çağrıları, araç sonuçları ve sistem olayları için application/json
contentdize | nesneKonuş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_mstam sayıÇağrı başlangıcından itibaren milisaniye cinsinden ofsetler. Ses zamanlaması bilindiğinde bulunur
ttfa_mstam sayıÖlçüldüğünde, bir model turu için ilk sese kadar geçen süre
audio_url, audio_urlsdize / diziTur 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 transcripts yerine history altı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_number alanlarını atlayarak origin_domain ekler.
  • Builder mikrofon test çağrıları eski yolda telephony.complete olarak raporlanır (uç nokta sistemi bunları web.complete olarak 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

Python (FastAPI)
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}
Node.js (Express)
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ı

CRM entegrasyonu

Her aramanın transkriptini ve kayıt URL'sini müşteri kayıtlarınızla birlikte saklayın.

Analiz

Konu modelleme, CSAT sinyali çıkarma veya aktarım oranı izleme için transkriptleri bir işlem hattına aktarın.

Kalite incelemesi

İnsan incelemesi için aramaları bir QA aracında açın veya kendi değerlendirme modelinizden geçirin.

Bildirimler

Aktarım / başarısızlık durumunda insan ekip arkadaşınızı tetikleyin.


İlgili