---
title: "telephony.complete / web.complete"
description: "Webhook χωρίς αποκλεισμό που παραδίδεται όταν τερματίζεται μια κλήση, με απομαγνητοφώνηση, URL εγγραφής και μετρήσεις."
---

Ένα συμβάν ολοκλήρωσης ενεργοποιείται μετά το τέλος κάθε κλήσης — εισερχόμενη τηλεφωνική κλήση,
εξερχόμενη τηλεφωνική κλήση, διαδικτυακή κλήση ή δοκιμαστική κλήση (συνεδρία μικροφώνου του εργαλείου δημιουργίας). Είναι
**μη αποκλειστικό**: απαντήστε με οποιονδήποτε κωδικό 2xx.

Το συμβάν παραδίδεται και από τις δύο διαδρομές:

* **Τα [τελικά σημεία webhook](/el/webhooks/endpoints)** λαμβάνουν
  `telephony.complete` (τηλεφωνικές κλήσεις) ή `web.complete` (διαδικτυακές κλήσεις και
  δοκιμαστικές κλήσεις μικροφώνου του εργαλείου δημιουργίας) με το σταθερό φορτίο που τεκμηριώνεται παρακάτω,
  ένα `event_id` ανά παράδοση, χρονικό όριο 30 δευτ. και
  [επαναλήψεις για έως 24 ώρες](/el/webhooks/overview).
* **Το [παλαιού τύπου webhook με ένα URL](/api-reference/organizations#legacy-single-url-webhook)**
  λαμβάνει μία σύγχρονη προσπάθεια (χρονικό όριο 10 δευτ., χωρίς επαναλήψεις) με ένα
  ελαφρώς διαφορετικό φορτίο — δείτε
  [Διαφορές φορτίου παλαιού τύπου](#legacy-payload-differences).

## Φορτίο αιτήματος (παραδόσεις 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_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,
    "unresolved_variables": ["campaign_owner"],
    "variables": {"campaign_name": "Spring renewals"},
    "voice": "john"
  },
  "event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
  "type": "telephony.complete"
}
```

| Πεδίο | Τύπος | Περιγραφή |
|-------|------|-------------|
| `call_id` | ακέραιος | Σταθερό σε κάθε συμβάν για αυτή την κλήση |
| `agent_id` | ακέραιος \| null | Ο πράκτορας που χειρίστηκε την κλήση, όταν είχε εκχωρηθεί κάποιος |
| `agent_name` | συμβολοσειρά \| null | Ο πράκτορας που χειρίστηκε την κλήση, όταν είχε εκχωρηθεί κάποιος |
| `direction` | συμβολοσειρά | `inbound`, `outbound`, `web`, `test`. Τα ιστορικά φορτία μπορεί να περιέχουν τις παλαιότερες τιμές `mic` ή `widget` |
| `from_number`, `to_number` | συμβολοσειρά | E.164. Το `from_number` είναι κυριολεκτικά `"web"` για κλήσεις ιστού και δοκιμαστικές κλήσεις |
| `origin_domain` | συμβολοσειρά | **Μόνο για web/test** — η προέλευση της σελίδας που φιλοξενούσε το widget (κενό για συνεδρίες mic) |
| `start_time`, `end_time` | χρονική σήμανση | ISO 8601 UTC |
| `duration_seconds` | ακέραιος \| null | Προκύπτει από την ώρα έναρξης/λήξης |
| `status` | συμβολοσειρά | `completed` ή `failed` |
| `end_reason` | συμβολοσειρά | Δείτε τον παρακάτω πίνακα |
| `product`, `voice` | συμβολοσειρά | Η διαμόρφωση του πράκτορα που ίσχυε κατά τον χρόνο της κλήσης |
| `variables` | αντικείμενο | Μεταβλητές εισόδου όπως καταγράφηκαν κατά την έναρξη της κλήσης |
| `unresolved_variables` | πίνακας | Ονόματα μεταβλητών που αναφέρονται στη διαμόρφωση της κλήσης αλλά δεν δόθηκαν κατά την έναρξη της κλήσης |
| `transfer_number` | συμβολοσειρά \| null | Ορίζεται όταν η κλήση μεταβιβάστηκε |
| `recording_url` | συμβολοσειρά \| null | Υπογεγραμμένο URL με λήξη· πραγματοποιήστε λήψη άμεσα. `null` όταν δεν υπάρχει διαθέσιμο στοιχείο εγγραφής |
| `billable_minutes` | αριθμός | Χρεώσιμα λεπτά, στρογγυλοποιημένα στο πλησιέστερο τέταρτο του λεπτού (διαστήματα 15 δευτερολέπτων, ελάχιστο 0.25). Οι κλήσεις που μεταβαίνουν απευθείας στον τηλεφωνητή εξακολουθούν να αναφέρουν εδώ τα πραγματικά μετρημένα λεπτά τους, αλλά η χρέωση περιορίζεται σε ένα λεπτό με την τιμή του προγράμματος. |
| `billing_total_cents` | ακέραιος | Σεντ USD |
| `transcripts` | πίνακας | Καταχωρίσεις απομαγνητοφώνησης ανά σειρά ομιλίας· μπορεί να είναι κενός όταν δεν είναι διαθέσιμη απομαγνητοφώνηση |
| `extracted_data` | αντικείμενο \| null | Αποτέλεσμα δομημένης εξαγωγής με `status`, `fields`, `evidence`, `verification`, `field_reasons` και `schema_version`. Κάθε πεδίο που δεν είναι null περιλαμβάνει το ακριβές απόσπασμα που ελέγχθηκε δομικά (έως 1.000 χαρακτήρες), μαζί με τον ρόλο ομιλητή και τον δείκτη σειράς ομιλίας· μεγαλύτερα αποσπάσματα που επιστρέφονται από το μοντέλο απορρίπτονται αντί να περικόπτονται. Το evidence είναι `null` όταν το πεδίο είναι `null`. Το `verified` σημαίνει ότι κάθε υποψήφιο έλαβε ακριβώς μία έγκυρη ανεξάρτητη ετυμηγορία. Το `unavailable` καλύπτει επίσης εσφαλμένη ή μερική έξοδο του επαληθευτή· οι έγκυρες μερικές ετυμηγορίες εξακολουθούν να εφαρμόζονται, ενώ τα υποψήφια χωρίς μία έγκυρη ετυμηγορία μηδενίζονται. Το `status` είναι `completed`, `failed`, `exhausted`, `skipped` ή `skipped_recording_disabled`· `null` όταν ο πράκτορας δεν είχε πεδία εξαγωγής |

### Λόγοι τερματισμού

| Τιμή | Σημασία |
|-------|---------|
| `user_hangup` | Ο απομακρυσμένος συνομιλητής έκλεισε πρώτος |
| `ai_hangup` | Ο AI πράκτορας τερμάτισε σκόπιμα την κλήση |
| `ai_transfer` | Ο AI πράκτορας μεταβίβασε την κλήση· το `transfer_number` έχει οριστεί |
| `ai_warm_transfer` | Ο AI πράκτορας ολοκλήρωσε μια θερμή μεταβίβαση με συμμετοχή |
| `voicemail_hangup` | Εντοπίστηκε τηλεφωνητής και η κλήση τερματίστηκε σύμφωνα με το `voicemail_action` σας |
| `max_duration` | Η κλήση έφτασε το μέγιστο όριο διάρκειας |
| `superseded` | Η συνεδρία αντικαταστάθηκε από νεότερη |
| `unknown` | Δεν ήταν δυνατό να προσδιοριστεί ο λόγος τερματισμού |

## Μορφή απομαγνητοφώνησης

Κάθε καταχώριση στο `transcripts` είναι μία εναλλαγή συνομιλίας. Οι ρόλοι είναι
`user` (ομιλία καλούντος), `model` (ομιλία πράκτορα **και** κλήσεις εργαλείων),
`tool` (αποτελέσματα εργαλείων) και `system` (συμβάντα κλήσης, όπως αλλαγές
γλώσσας).

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

| Πεδίο | Τύπος | Περιγραφή |
|-------|------|-------------|
| `role` | συμβολοσειρά | `user`, `model`, `tool` ή `system` |
| `content_type` | συμβολοσειρά | `text/plain` για ομιλία· `application/json` για κλήσεις εργαλείων, αποτελέσματα εργαλείων και συμβάντα συστήματος |
| `content` | συμβολοσειρά \| αντικείμενο | Κείμενο ομιλίας ή το δομημένο αντικείμενο που εμφανίζεται παραπάνω. Κλήσεις εργαλείων: `{"tool_call": name, "arguments": {…}}`. Αποτελέσματα εργαλείων: `{"tool_name": name, "response": {…}}` |
| `start_ms`, `end_ms` | ακέραιος | Μετατοπίσεις από την έναρξη της κλήσης, σε ms. Υπάρχουν όταν είναι γνωστός ο χρονισμός ήχου |
| `ttfa_ms` | ακέραιος | Χρόνος έως τον πρώτο ήχο για μια εναλλαγή `model`, όταν μετράται |
| `audio_url`, `audio_urls` | συμβολοσειρά / πίνακας | Υπογεγραμμένα URL με λήξη για τον ήχο της εναλλαγής, όταν η εγγραφή γίνεται ανά εναλλαγή |

Για το πλήρως δομημένο ιστορικό εναλλαγών (με δείκτες διακοπής,
προτροπές επιβεβαίωσης και ακατέργαστες θέσεις), χρησιμοποιήστε το
[`GET /v1/calls/{call_id}/history`](/api-reference/calls#get-history).

## Διαφορές παλαιού ωφέλιμου φορτίου

Το παλαιό περιτύλιγμα webhook ενός URL είναι
`{"type": "telephony.complete" | "web.complete", "data": {…}}` χωρίς
**`event_id`**, και το `data` του διαφέρει από το ωφέλιμο φορτίο του τελικού σημείου:

Το παλαιό ωφέλιμο φορτίο ολοκλήρωσης περιλαμβάνει επίσης τα `agent_id` και `agent_name`.

* Ο πίνακας εναλλαγών βρίσκεται στο **`history`**, όχι στο `transcripts` (το ίδιο
  σχήμα εναλλαγής όπως παραπάνω).
* Το σύνολο πεδίων είναι η ακατέργαστη αναφορά τέλους κλήσης και μπορεί να περιλαμβάνει
  επιπλέον εσωτερικά πεδία πέρα από τον παραπάνω πίνακα — αντιμετωπίζετε τα άγνωστα
  πεδία ως πληροφοριακά.
* Οι διαδικτυακές κλήσεις (`direction: "web"`) **παραλείπουν** τα `from_number` / `to_number`
  και προσθέτουν το `origin_domain`.
* Οι δοκιμαστικές κλήσεις μικροφώνου του Builder αναφέρονται ως `telephony.complete` στην παλαιά
  διαδρομή (το σύστημα τελικού σημείου τις αντιστοιχίζει σε `web.complete`).
* **Συντονισμός μεταφοράς:** όταν μια κλήση ολοκληρώνεται με μεταφορά, το
  παλαιό webhook καλείται συγχρονισμένα και μπορεί να απαντήσει
  `{"transfer_ready": false}` για να υποδείξει ότι ο προορισμός μεταφοράς δεν είναι
  έτοιμος. Οποιαδήποτε άλλη απάντηση (ή η απουσία παλαιού webhook) επιτρέπει τη συνέχιση της
  μεταφοράς. Οι παραδόσεις τελικού σημείου δεν λαμβάνονται ποτέ υπόψη για αυτό.

---

## Παράδειγμα χειριστή

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

---

## Συνήθεις περιπτώσεις χρήσης

<CardGroup cols={2}>
  <Card title="Ενσωμάτωση CRM" icon="database">
    Αποθηκεύστε το απομαγνητοφωνημένο κείμενο και το URL εγγραφής κάθε κλήσης μαζί με τις
    εγγραφές πελατών σας.
  </Card>
  <Card title="Αναλύσεις" icon="chart-line">
    Μεταδώστε απομαγνητοφωνημένα κείμενα σε μια ροή επεξεργασίας για μοντελοποίηση θεμάτων, εξαγωγή
    ενδείξεων CSAT ή παρακολούθηση του ποσοστού μεταφορών.
  </Card>
  <Card title="Έλεγχος ποιότητας" icon="clipboard-check">
    Ανοίξτε κλήσεις σε ένα εργαλείο QA για ανθρώπινο έλεγχο ή εκτελέστε τις μέσω του
    δικού σας μοντέλου αξιολόγησης.
  </Card>
  <Card title="Ειδοποιήσεις" icon="bell">
    Ενεργοποιήστε έναν ανθρώπινο συνεργάτη σε περίπτωση μεταφοράς ή αποτυχίας.
  </Card>
</CardGroup>

---

## Σχετικά

<CardGroup cols={2}>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/el/webhooks/call-incoming">
    Το αντίστοιχο μπλοκαριστικό συμβάν που εκτελείται στην έναρξη της κλήσης.
  </Card>
  <Card title="Κατάλογος συμβάντων" icon="list" href="/el/webhooks/events">
    Άλλοι τύποι συμβάντων στους οποίους μπορείτε να εγγραφείτε.
  </Card>
  <Card title="API ιστορικού κλήσεων" icon="phone" href="/api-reference/calls">
    Τα ίδια δεδομένα είναι προσβάσιμα μέσω REST για αναδρομική συμπλήρωση ή επανάληψη.
  </Card>
</CardGroup>
