ThunderPhone 2.0 متاح الآن.خدمة ذاتية، ابتداءً من 2 سنت/دقيقة.اقرأ الإعلان

Webhooks

telephony.complete / web.complete

خطاف ويب غير حاجب يُسلَّم عند انتهاء مكالمة، مع نص مفرغ ورابط التسجيل والمقاييس.

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

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

نص الطلب (تسليمات نقطة النهاية)

{
  "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 في الوكيل) وينجح الإنشاء؛ وإلا تكون القيمة null، بما في ذلك المكالمات القصيرة جدًا التي لا يمكن تلخيصها، والمكالمات دون نص مفرغ، والوكلاء الذين أُوقف التسجيل لديهم. يُنشأ الملخص في خطوة ما بعد المكالمة نفسها التي يتم فيها الاستخراج، لذا يحدد إعداد انتظار البيانات المستخرجة قبل إرسال خطاف الويب لانتهاء المكالمة (extraction_blocks_completion) ما إذا كان هذا الحدث ينتظره: في وضع الانتظار يكون الملخص جاهزًا قبل إرسال هذا الحدث (ويُمنح الملخص نفسه 30 ثانية كحد أقصى ضمن ميزانية الخطوة البالغة 75 ثانية)؛ وعندما لا ينتظر الحدث (never، أو تحويل بموجب except_transfers) تكون قيمة هذا الحقل null ولا يُعاد إرسال الملخص في call.data_extracted، لذا اقرأه لاحقًا من summary في كائن المكالمة. متاح أيضًا في صفحة المكالمة في لوحة التحكم
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 (أحداث المكالمة مثل تبديل اللغة).

[
  {
    "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.

اختلافات الحمولة القديمة

غلاف 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 قديم) متابعة التحويل. لا تتم استشارة عمليات تسليم نقطة النهاية لهذا الغرض.

مثال على معالج

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}
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 });
  },
);

حالات الاستخدام الشائعة

التكامل مع CRM

احفظ النص المفرغ لكل مكالمة ورابط التسجيل إلى جانب سجلات عملائك.

التحليلات

أرسل النصوص المفرغة تدفقياً إلى مسار عمل لنمذجة الموضوعات، واستخراج مؤشرات CSAT، أو مراقبة معدل التحويل.

مراجعة الجودة

افتح المكالمات في أداة لضمان الجودة لمراجعتها بشرياً، أو مررها عبر نموذج التقييم الخاص بك.

الإشعارات

نبّه زميلاً بشرياً عند التحويل أو الفشل.


ذو صلة