---
title: "telephony.complete / web.complete"
description: "Bir çağrı sona erdiğinde, transkript, kayıt URL'si ve metriklerle birlikte gönderilen engellemeyen webhook."
---

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ı](/tr/webhooks/endpoints)**, 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](/tr/webhooks/overview) ile
  `telephony.complete` (telefon çağrıları) veya `web.complete` (web çağrıları ve
  oluşturucu mikrofon test çağrıları) alır.
* **[Eski tek URL webhook'u](/api-reference/organizations#legacy-single-url-webhook)**
  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ı](#legacy-payload-differences).

## İstek yükü (uç nokta teslimatları)

```json
{
  "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](/tr/guides/call-transfers) 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](/api-reference/agents) ü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](/api-reference/calls#call-object) ü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](/tr/webhooks/call-incoming) 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.

```json
[
  {
    "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`](/api-reference/calls#get-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

<CodeGroup>
```python 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}
```

```javascript 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 });
  },
);
```
</CodeGroup>

---

## Yaygın kullanım alanları

<CardGroup cols={2}>
  <Card title="CRM entegrasyonu" icon="database">
    Her aramanın transkriptini ve kayıt URL'sini müşteri kayıtlarınızla birlikte saklayın.
  </Card>
  <Card title="Analiz" icon="chart-line">
    Konu modelleme, CSAT sinyali çıkarma veya aktarım oranı izleme için transkriptleri bir işlem hattına aktarın.
  </Card>
  <Card title="Kalite incelemesi" icon="clipboard-check">
    İnsan incelemesi için aramaları bir QA aracında açın veya kendi değerlendirme modelinizden geçirin.
  </Card>
  <Card title="Bildirimler" icon="bell">
    Aktarım / başarısızlık durumunda insan ekip arkadaşınızı tetikleyin.
  </Card>
</CardGroup>

---

## İlgili

<CardGroup cols={2}>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/tr/webhooks/call-incoming">
    Arama başladığında çalışan, engelleyici karşılığı.
  </Card>
  <Card title="Etkinlik kataloğu" icon="list" href="/tr/webhooks/events">
    Abone olabileceğiniz diğer etkinlik türleri.
  </Card>
  <Card title="Arama geçmişi API'si" icon="phone" href="/api-reference/calls">
    Geri doldurma / yeniden oynatma için REST üzerinden erişilebilen aynı veriler.
  </Card>
</CardGroup>
