---
title: "telephony.complete / web.complete"
description: "Webhook לא חוסם שנשלח כאשר שיחה מסתיימת, עם תמליל, כתובת URL של ההקלטה ומדדים."
---

אירוע השלמה מופעל לאחר סיום כל שיחה — טלפוניה נכנסת,
טלפוניה יוצאת, שיחת ווב או שיחת בדיקה (הפעלת מיקרופון בבונה). הוא
**אינו חוסם**: השיבו עם כל קוד 2xx.

האירוע נמסר בשני המסלולים:

* **[נקודות קצה של Webhook](/he/webhooks/endpoints)** מקבלות
  `telephony.complete` (שיחות טלפון) או `web.complete` (שיחות ווב ושיחות
  בדיקת מיקרופון בבונה) עם המטען היציב המתועד להלן,
  `event_id` לכל מסירה, פסק זמן של 30 שניות, ו-
  [ניסיונות חוזרים למשך עד 24 שעות](/he/webhooks/overview).
* **ה-[Webhook מדור קודם עם כתובת URL יחידה](/api-reference/organizations#legacy-single-url-webhook)**
  מקבל ניסיון סינכרוני אחד (פסק זמן של 10 שניות, ללא ניסיונות חוזרים) עם
  מטען שונה במקצת — ראו
  [הבדלים במטען מדור קודם](#legacy-payload-differences).

## מטען בקשה (מסירות לנקודת קצה)

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

| שדה | סוג | תיאור |
|-------|------|-------------|
| `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` | מחרוזת | **לאינטרנט/בדיקה בלבד** — מקור הדף שאירח את הווידג'ט (ריק עבור הפעלות מיקרופון) |
| `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` כאשר אין ארטיפקט הקלטה זמין |
| `recording_stopped_at` | חותמת זמן \| null | המועד שבו ההקלטה נעצרה לפני שהשיחה הסתיימה; שום דבר לאחר רגע זה אינו נכלל בהקלטה. `null` כאשר ההקלטה נמשכה עד סוף השיחה או כאשר השיחה לא הוקלטה |
| `recording_stopped_reason` | מחרוזת \| null | הסיבה לכך שההקלטה נעצרה מוקדם. `warm_transfer`: ההקלטה נעצרת ממש לפני ש[העברה חמה](/he/guides/call-transfers) מתקשרת ליעד, ונשארת כבויה גם אם ההעברה נכשלת והמתקשר חוזר לסוכן. התייחסו לערכים אחרים כמידע בלבד; ייתכן שיתווספו ערכים נוספים. `null` כאשר `recording_stopped_at` הוא `null` |
| `billable_minutes` | מספר | דקות מחויבות, מעוגלות לרבע הדקה הקרוב ביותר (מרווחים של 15 שניות, מינימום 0.25). שיחות ישירות לתא קולי עדיין מדווחות כאן על הדקות הנמדדות בפועל, אך החיוב מוגבל לדקה אחת לפי תעריף התוכנית. |
| `billing_total_cents` | מספר שלם | סנטים של USD |
| `transcripts` | מערך | רשומות תמלול לכל תור; יכול להיות ריק כאשר תמלול אינו זמין |
| `summary` | מחרוזת \| null | סיכום לאחר שיחה בשפה פשוטה (שתיים עד ארבעה משפטי פרוזה, עד 2,000 תווים, הכתובים בשפת השיחה: השפה הראשית של הסוכן אלא אם השיחה החליפה אותה) כאשר האפשרות **יצירת סיכום שיחה** מופעלת אצל הסוכן תחת **נתונים לאיסוף** בבונה הסוכנים (`call_summary_enabled` ב[סוכן](/api-reference/agents)) והיצירה הצליחה; אחרת `null`, כולל בשיחות קצרות מדי לסיכום, שיחות ללא תמלול וסוכנים שההקלטה אצלם מושבתת. הסיכום נוצר באותו שלב לאחר השיחה שבו מתבצעת החילוץ, לכן ההגדרה **המתנה לנתונים שחולצו לפני שליחת הוובהוק של סיום השיחה** (`extraction_blocks_completion`) קובעת אם אירוע זה ממתין לו: במצב המתנה הסיכום מוכן לפני שליחת אירוע זה (לסיכום עצמו מוקצות לכל היותר 30 שניות מתוך תקציב של 75 שניות לשלב); כאשר האירוע אינו ממתין (`never`, או העברה במסגרת `except_transfers`) שדה זה הוא `null` והסיכום **אינו** נשלח מחדש ב-`call.data_extracted`, לכן קראו אותו מאוחר יותר מתוך `summary` ב[אובייקט השיחה](/api-reference/calls#call-object). זמין גם בדף השיחה בלוח הבקרה |
| `extracted_data` | אובייקט \| null | תוצאת חילוץ מובנית הכוללת `status`, `fields`, `evidence`, `verification`, `field_reasons` ו-`schema_version`. לכל שדה שאינו null יש ציטוט מדויק שנבדק מבנית (עד 1,000 תווים), וכן תפקיד הדובר ואינדקס התור שלו; ציטוטים ארוכים יותר שהוחזרו מהמודל נדחים במקום להיחתך. הראיה היא `null` כאשר השדה הוא `null`. `verified` מציין שכל מועמד קיבל בדיוק הכרעה עצמאית תקפה אחת. `unavailable` כולל גם פלט מאמת פגום או חלקי; הכרעות חלקיות תקפות עדיין חלות, בעוד מועמדים ללא הכרעה תקפה אחת מאופסים ל-null. `status` הוא `completed`, `failed`, `exhausted`, `skipped` או `skipped_recording_disabled`; `null` כאשר לסוכן לא היו שדות חילוץ |
| `steps_trace` | מערך | רק בשיחות שהסוכן שלהן כולל שלבים או העברות. הסדר שבו השיחה עברה דרכם: לכל רשומה יש `t_ms` (מילישניות מאז תחילת השיחה), `agent` (מפתח הסוכן, `a` בתוספת המזהה שלו), `step` (מזהה השלב, או `null` בהעברה לסוכן ללא שלבים) ו-`via` (`start`, `model`, `condition`, `on_enter` או `handoff`). מכיל מזהים וזמנים בלבד, ולכן נשמר גם כאשר ההקלטה כבויה |
| `collected_values` | אובייקט | רק בשיחות שהסוכן שלהן כולל שלבים או העברות. הערכים שהסוכן אסף בשלבים שלו, עם מפתח לפי שם שדה האיסוף, וכן `<tool>_result` עבור תוצאת `on_enter_tool` של כל שלב. מושמט כאשר תוכן השיחה אינו נשמר (ההקלטה מושבתת) |

### סיבות סיום

| ערך | משמעות |
|-------|---------|
| `user_hangup` | הצד המרוחק ניתק ראשון |
| `ai_hangup` | הבינה המלאכותית סיימה את השיחה בכוונה |
| `ai_transfer` | הבינה המלאכותית העבירה את השיחה; `transfer_number` מוגדר |
| `ai_warm_transfer` | הבינה המלאכותית השלימה העברה חמה (מלווה) |
| `voicemail_hangup` | זוהתה תא קולי והשיחה הסתיימה בהתאם ל-`voicemail_action` שלכם |
| `max_duration` | השיחה הגיעה למגבלת משך הזמן המרבי: המגבלה של הסוכן, המגבלה של השיחה עצמה, או מגבלת הפלטפורמה של 6 שעות. שיחה שהבינה המלאכותית או המתקשר מסיימים לאחר אזהרת מגבלת הזמן שומרת על `ai_hangup` או `user_hangup` |
| `superseded` | ההפעלה הוחלפה בהפעלה חדשה יותר |
| `inbound_config_webhook_failed` | שיחה נכנסת למספר במצב webhook: ה-[webhook של התצורה](/he/webhooks/call-incoming) לא החזיר תצורה שניתן להשתמש בה, ולכן השיחה הסתיימה לפני שסוכן ענה. הערך של `status` הוא `failed` והשיחה אינה מחויבת |
| `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` | מספר שלם | היסטים מתחילת השיחה, באלפיות שנייה. קיימים כאשר תזמון האודיו ידוע |
| `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="/he/webhooks/call-incoming">
    המקביל החוסם שפועל בתחילת השיחה.
  </Card>
  <Card title="קטלוג אירועים" icon="list" href="/he/webhooks/events">
    סוגי אירועים נוספים שאפשר להירשם אליהם.
  </Card>
  <Card title="API היסטוריית שיחות" icon="phone" href="/api-reference/calls">
    אותם נתונים זמינים דרך REST למילוי נתונים חסרים או להפעלה חוזרת.
  </Card>
</CardGroup>
