---
title: "telephony.complete / web.complete"
description: "خطاف ويب غير حاجب يُسلَّم عند انتهاء مكالمة، مع نص مفرغ ورابط التسجيل والمقاييس."
---

يتم تشغيل حدث الإكمال بعد انتهاء كل مكالمة — الاتصالات الهاتفية الواردة،
والاتصالات الهاتفية الصادرة، أو مكالمة الويب، أو مكالمة اختبار (جلسة ميكروفون أداة الإنشاء). وهو
**غير حاجب**: استجب بأي رمز 2xx.

يتم تسليم الحدث عبر كلا المسارين:

* تستقبل **[نقاط نهاية Webhook](/ar/webhooks/endpoints)**
  `telephony.complete` (المكالمات الهاتفية) أو `web.complete` (مكالمات الويب ومكالمات اختبار ميكروفون أداة الإنشاء) مع
  الحمولة المستقرة الموضحة أدناه، و`event_id` لكل عملية تسليم، ومهلة قدرها 30 ثانية، و
  [إعادات محاولة لمدة تصل إلى 24 ساعة](/ar/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_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` عند عدم توفر تسجيل |
| `billable_minutes` | رقم | الدقائق المفوترة، مقربة إلى أقرب ربع دقيقة (بزيادات 15 ثانية، بحد أدنى 0.25). تظل المكالمات التي تنتقل مباشرة إلى البريد الصوتي تُبلغ عن دقائقها الفعلية المقاسة هنا، لكن تُحدَّد الرسوم بحد أقصى دقيقة واحدة وفق سعر الباقة. |
| `billing_total_cents` | عدد صحيح | سنتات الدولار الأمريكي |
| `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` أيضًا مخرجات المدقق المشوهة أو الجزئية؛ تظل الأحكام الجزئية الصالحة مطبقة، بينما تُحوَّل إلى قيمة خالية المرشحات التي لا تملك حكمًا صالحًا واحدًا. تكون `status` إحدى القيم `completed` أو `failed` أو `exhausted` أو `skipped` أو `skipped_recording_disabled`؛ وتكون `null` عندما لا تكون لدى الوكيل حقول استخراج |

### أسباب الإنهاء

| القيمة | المعنى |
|-------|---------|
| `user_hangup` | أنهى الطرف البعيد المكالمة أولًا |
| `ai_hangup` | أنهى الذكاء الاصطناعي المكالمة عمدًا |
| `ai_transfer` | حوّل الذكاء الاصطناعي المكالمة؛ وتم تعيين `transfer_number` |
| `ai_warm_transfer` | أكمل الذكاء الاصطناعي تحويلًا دافئًا (بحضور الموظف) |
| `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` | عدد صحيح | الإزاحات ابتداءً من بدء المكالمة، بالمللي ثانية. تكون موجودة عند معرفة توقيت الصوت |
| `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`.
* تُبلّغ مكالمات اختبار الميكروفون في أداة الإنشاء بصيغة `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">
    احفظ النص المفرغ لكل مكالمة ورابط التسجيل إلى جانب سجلات
    عملائك.
  </Card>
  <Card title="التحليلات" icon="chart-line">
    أرسل النصوص المفرغة تدفقياً إلى مسار عمل لنمذجة الموضوعات، واستخراج
    مؤشرات CSAT، أو مراقبة معدل التحويل.
  </Card>
  <Card title="مراجعة الجودة" icon="clipboard-check">
    افتح المكالمات في أداة لضمان الجودة لمراجعتها بشرياً، أو مررها عبر
    نموذج التقييم الخاص بك.
  </Card>
  <Card title="الإشعارات" icon="bell">
    نبّه زميلاً بشرياً عند التحويل أو الفشل.
  </Card>
</CardGroup>

---

## ذو صلة

<CardGroup cols={2}>
  <Card title="telephony.incoming / web.incoming" icon="phone" href="/ar/webhooks/call-incoming">
    النظير الحاجب الذي يعمل عند بدء المكالمة.
  </Card>
  <Card title="كتالوج الأحداث" icon="list" href="/ar/webhooks/events">
    أنواع أحداث أخرى يمكنك الاشتراك فيها.
  </Card>
  <Card title="واجهة برمجة تطبيقات سجل المكالمات" icon="phone" href="/api-reference/calls">
    البيانات نفسها متاحة عبر REST للتعبئة اللاحقة أو إعادة التشغيل.
  </Card>
</CardGroup>
