---
title: "telephony.complete / web.complete"
description: "Webhook neblocant trimis la încheierea unui apel, cu transcrierea, URL-ul înregistrării și metrici."
---

Un eveniment de finalizare este declanșat după încheierea fiecărui apel — telefonie de intrare,
telefonie de ieșire, apel web sau apel de test (sesiune cu microfonul în builder). Acesta
**nu blochează**: răspundeți cu orice cod 2xx.

Evenimentul este livrat prin ambele mecanisme:

* **[Endpointurile webhook](/ro/webhooks/endpoints)** primesc
  `telephony.complete` (apeluri telefonice) sau `web.complete` (apeluri web și
  apeluri de test cu microfonul în builder), cu încărcătura stabilă documentată mai jos,
  un `event_id` pentru fiecare livrare, un timeout de 30 s și
  [reîncercări timp de până la 24 h](/ro/webhooks/overview).
* **[Webhookul vechi cu un singur URL](/api-reference/organizations#legacy-single-url-webhook)**
  primește o singură încercare sincronă (timeout de 10 s, fără reîncercări), cu o
  încărcătură ușor diferită — consultați
  [Diferențe ale încărcăturii vechi](#legacy-payload-differences).

## Corpul cererii (livrări către endpoint)

```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"
}
```

| Câmp | Tip | Descriere |
|-------|------|-------------|
| `call_id` | integer | Stabil în toate evenimentele pentru acest apel |
| `agent_id` | integer \| null | Agentul care a gestionat apelul, atunci când a fost alocat unul |
| `agent_name` | string \| null | Agentul care a gestionat apelul, atunci când a fost alocat unul |
| `direction` | string | `inbound`, `outbound`, `web`, `test`. Încărcăturile istorice pot conține valorile vechi `mic` sau `widget` |
| `from_number`, `to_number` | string | E.164. `from_number` este valoarea literală `"web"` pentru apelurile web și apelurile de test |
| `origin_domain` | string | **Doar pentru web/test** — originea paginii care a găzduit widgetul (goală pentru sesiunile mic) |
| `start_time`, `end_time` | timestamp | ISO 8601 UTC |
| `duration_seconds` | integer \| null | Derivat din ora de început/ora de încheiere |
| `status` | string | `completed` sau `failed` |
| `end_reason` | string | Consultați tabelul de mai jos |
| `product`, `voice` | string | Configurația agentului activă la momentul apelului |
| `variables` | object | Variabile de intrare instantaneizate la începutul apelului |
| `unresolved_variables` | array | Numele variabilelor la care face referire configurația apelului, dar care nu au fost furnizate la începutul apelului |
| `transfer_number` | string \| null | Setat când apelul a fost transferat |
| `recording_url` | string \| null | URL semnat cu expirare; descărcați-l prompt. `null` când nu este disponibil niciun artefact de înregistrare |
| `recording_stopped_at` | timestamp \| null | Momentul în care înregistrarea a fost oprită înainte de încheierea apelului; nimic de după acest moment nu este inclus în înregistrare. `null` când înregistrarea a continuat până la finalul apelului sau apelul nu a fost înregistrat |
| `recording_stopped_reason` | string \| null | Motivul pentru care înregistrarea s-a oprit anticipat. `warm_transfer`: se oprește chiar înainte ca un [transfer asistat](/ro/guides/call-transfers) să apeleze destinația și rămâne oprită chiar dacă transferul eșuează și apelantul revine la agent. Tratați celelalte valori ca fiind informative; pot fi adăugate mai multe. `null` când `recording_stopped_at` este `null` |
| `billable_minutes` | number | Minute facturate, rotunjite la cel mai apropiat sfert de minut (incrementări de 15 secunde, minimum 0,25). Apelurile redirecționate direct către mesageria vocală raportează în continuare aici minutele lor efective contorizate, însă taxa este plafonată la un minut la tariful planului. |
| `billing_total_cents` | integer | Cenți USD |
| `transcripts` | array | Intrări de transcriere pentru fiecare tură; poate fi gol când o transcriere nu este disponibilă |
| `summary` | string \| null | Rezumat post-apel în limbaj natural (două până la patru propoziții de proză, maximum 2.000 de caractere, redactat în limba apelului: limba principală a agentului, cu excepția cazului în care apelul a suprascris-o) când agentul are activată opțiunea **Generați un rezumat al apelului** în secțiunea **Date de colectat** din constructorul de agenți (`call_summary_enabled` pe [Agent](/api-reference/agents)) și generarea a reușit; `null` în caz contrar, inclusiv pentru apeluri prea scurte pentru a fi rezumate, apeluri fără transcriere și agenți cu înregistrarea dezactivată. Rezumatul este generat în aceeași etapă post-apel ca extragerea, astfel că setarea **Așteptați datele extrase înainte de a trimite webhookul de finalizare a apelului** (`extraction_blocks_completion`) decide dacă acest eveniment îl așteaptă: într-un mod de așteptare, rezumatul este gata înainte ca acest eveniment să fie trimis (rezumatul însuși primește maximum 30 de secunde din bugetul de 75 de secunde al etapei); când evenimentul nu așteaptă (`never` sau un transfer în cadrul `except_transfers`), acest câmp este `null`, iar rezumatul **nu** este retrimis în `call.data_extracted`, așa că citiți-l ulterior din `summary` pe [obiectul Call](/api-reference/calls#call-object). De asemenea, disponibil pe pagina apelului din tabloul de bord |
| `extracted_data` | object \| null | Rezultat structurat al extragerii cu `status`, `fields`, `evidence`, `verification`, `field_reasons` și `schema_version`. Fiecare câmp nenul are citatul exact verificat structural (maximum 1.000 de caractere), precum și rolul vorbitorului și indexul turei sale; citatele mai lungi returnate de model sunt respinse în loc să fie trunchiate. Dovezile sunt `null` când câmpul este `null`. `verified` înseamnă că fiecare candidat a primit exact un verdict independent valid. `unavailable` acoperă și ieșirile incorecte sau parțiale ale verificatorului; verdictele parțiale valide se aplică în continuare, în timp ce candidații fără un verdict valid sunt setați la nul. `status` este `completed`, `failed`, `exhausted`, `skipped` sau `skipped_recording_disabled`; `null` când agentul nu avea câmpuri de extragere |
| `steps_trace` | array | Doar pentru apelurile al căror agent are pași sau transferuri între agenți. Ordinea în care apelul a trecut prin acestea: fiecare intrare are `t_ms` (milisecunde de la începutul apelului), `agent` (cheia agentului, `a` plus ID-ul său), `step` (ID-ul pasului sau `null` pentru un transfer către un agent fără pași) și `via` (`start`, `model`, `condition`, `on_enter` sau `handoff`). Conține doar ID-uri și momente, astfel că este păstrat chiar și când înregistrarea este dezactivată |
| `collected_values` | object | Doar pentru apelurile al căror agent are pași sau transferuri între agenți. Valorile colectate de agent în pașii săi, indexate după numele câmpului de colectare, plus `<tool>_result` pentru rezultatul `on_enter_tool` al fiecărui pas. Omis când conținutul apelului nu este păstrat (înregistrarea este dezactivată) |

### Motive de încheiere

| Valoare | Semnificație |
|-------|---------|
| `user_hangup` | Partea de la distanță a închis prima |
| `ai_hangup` | Agentul AI a încheiat apelul în mod deliberat |
| `ai_transfer` | Agentul AI a transferat apelul; `transfer_number` este setat |
| `ai_warm_transfer` | Agentul AI a finalizat un transfer asistat |
| `voicemail_hangup` | A fost detectată mesageria vocală, iar apelul s-a încheiat conform `voicemail_action` |
| `max_duration` | Apelul a atins limita maximă de durată: limita agentului, propria limită a apelului sau limita platformei de 6 ore. Un apel încheiat de agentul AI sau de apelant după avertismentul privind limita de timp păstrează `ai_hangup` sau `user_hangup` |
| `superseded` | Sesiunea a fost înlocuită de una mai nouă |
| `inbound_config_webhook_failed` | Apel primit către un număr în modul webhook: [webhook-ul de configurare](/ro/webhooks/call-incoming) nu a returnat o configurație utilizabilă, astfel că apelul a fost încheiat înainte ca un agent să răspundă. `status` este `failed`, iar apelul nu este facturat |
| `unknown` | Motivul încheierii nu a putut fi determinat |

## Formatul transcrierii

Fiecare intrare din `transcripts` reprezintă un tur conversațional. Rolurile sunt
`user` (vorbirea apelantului), `model` (vorbirea agentului **și** apelurile de instrumente),
`tool` (rezultatele instrumentelor) și `system` (evenimente ale apelului, cum ar fi
schimbările de limbă).

```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"] }
    }
  }
]
```

| Câmp | Tip | Descriere |
|-------|------|-------------|
| `role` | șir | `user`, `model`, `tool` sau `system` |
| `content_type` | șir | `text/plain` pentru vorbire; `application/json` pentru apeluri de instrumente, rezultate ale instrumentelor și evenimente de sistem |
| `content` | șir \| obiect | Textul vorbirii sau obiectul structurat prezentat mai sus. Apeluri de instrumente: `{"tool_call": name, "arguments": {…}}`. Rezultate ale instrumentelor: `{"tool_name": name, "response": {…}}` |
| `start_ms`, `end_ms` | întreg | Decalaje față de începutul apelului, în ms. Sunt prezente când sincronizarea audio este cunoscută |
| `ttfa_ms` | întreg | Timpul până la primul sunet pentru un tur `model`, când este măsurat |
| `audio_url`, `audio_urls` | șir / matrice | URL-uri semnate cu expirare pentru sunetul turului, când înregistrarea este realizată pentru fiecare tur |

Pentru istoricul complet structurat al tururilor (cu marcaje de întrerupere,
mesaje de confirmare și poziții brute), utilizați
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

## Diferențe ale payloadului moștenit

Plicul webhook moștenit cu un singur URL este
`{"type": "telephony.complete" | "web.complete", "data": {…}}` și nu are
**niciun `event_id`**, iar câmpul său `data` diferă de payloadul endpointului:

Payloadul moștenit de finalizare include și `agent_id` și `agent_name`.

* Matricea de tururi se află în **`history`**, nu în `transcripts` (aceeași schemă
  de tururi ca mai sus).
* Setul de câmpuri este raportul brut de final de apel și poate include
  câmpuri interne suplimentare dincolo de tabelul de mai sus — tratați câmpurile necunoscute
  ca informaționale.
* Apelurile web (`direction: "web"`) **omit** `from_number` / `to_number`
  și adaugă `origin_domain`.
* Apelurile de testare a microfonului din Builder sunt raportate ca `telephony.complete` pe calea
  moștenită (sistemul endpointului le mapează la `web.complete`).
* **Coordonarea transferului:** când un apel se încheie printr-un transfer,
  webhookul moștenit este apelat sincron și poate răspunde cu
  `{"transfer_ready": false}` pentru a semnala că ținta transferului nu este
  pregătită. Orice alt răspuns (sau lipsa unui webhook moștenit) permite continuarea transferului.
  Livrările către endpoint nu sunt consultate niciodată pentru aceasta.

---

## Exemplu de handler

<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>

---

## Cazuri de utilizare comune

<CardGroup cols={2}>
  <Card title="Integrare CRM" icon="database">
    Păstrați transcrierea și URL-ul înregistrării fiecărui apel alături de
    datele clienților dumneavoastră.
  </Card>
  <Card title="Analiză" icon="chart-line">
    Transmiteți transcrierile către un pipeline pentru modelarea subiectelor, extragerea
    semnalelor CSAT sau monitorizarea ratei de transfer.
  </Card>
  <Card title="Revizuirea calității" icon="clipboard-check">
    Deschideți apelurile într-un instrument QA pentru revizuire umană sau procesați-le prin
    propriul model de evaluare.
  </Card>
  <Card title="Notificări" icon="bell">
    Notificați un coleg uman la transfer / eșec.
  </Card>
</CardGroup>

---

## Resurse asociate

<CardGroup cols={2}>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/ro/webhooks/call-incoming">
    Echivalentul blocant care rulează la începutul apelului.
  </Card>
  <Card title="Catalog de evenimente" icon="list" href="/ro/webhooks/events">
    Alte tipuri de evenimente la care vă puteți abona.
  </Card>
  <Card title="API pentru istoricul apelurilor" icon="phone" href="/api-reference/calls">
    Aceleași date sunt accesibile prin REST pentru completare retrospectivă / reluare.
  </Card>
</CardGroup>
