ThunderPhone 2.0 yayında.Kendi başınıza kullanmaya başlayın; dakikada 2¢'den başlayan fiyatlarla.Duyuruyu okuyun

Webhooks

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:

İ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"
}
AlanTürAçıklama
call_idintegerBu arama için her olayda sabittir
directionstringinbound, outbound, web, test. Geçmiş yükler eski mic veya widget değerlerini içerebilir
from_number, to_numberstringE.164. from_number, web aramaları ve test aramaları için tam olarak "web" değeridir
origin_domainstringYalnızca web/test — widget'ı barındıran sayfanın kaynağıdır (mikrofon oturumları için boştur)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullBaşlangıç/bitiş zamanlarından türetilir
statusstringcompleted veya failed
end_reasonstringAşağıdaki tabloya bakın
product, voicestringArama sırasında etkin olan ajan yapılandırması
transfer_numberstring | nullArama aktarıldığında ayarlanır
recording_urlstring | nullSüresi dolan imzalı URL; hemen indirin. Kayıt yapıtı mevcut olmadığında null olur
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 aramalar burada gerçek ölçülen dakikalarını yine bildirir, ancak ücret plan oranında bir dakikayla sınırlanır.
billing_total_centsintegerUSD senti
transcriptsarrayTur başına transkript girdileri; transkript kullanılamadığında boş olabilir

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 ayarlanır
ai_warm_transferYapay zeka sıcak (katılımlı) aktarımı tamamladı
voicemail_hangupSesli mesaj algılandı ve arama voicemail_action uyarınca sonlandırıldı
max_durationArama maksimum süre sınırına ulaştı
supersededOturum daha yeni bir oturumla değiştirildi
unknownBitiş 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"] }
    }
  }
]
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_mstamsayıÇağrı başlangıcından itibaren ofsetler, ms. Ses zamanlaması bilindiğinde bulunur
ttfa_mstamsayıÖlçüldüğünde, bir model dönüşü için ilk sese kadar geçen süre
audio_url, audio_urlsdize / diziDö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 transcripts yerine history altı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_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. 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

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.

Analizler

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

Kalite incelemesi

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

Bildirimler

Aktarma veya başarısızlık durumunda bir insan ekip arkadaşını tetikleyin.


İlgili