ThunderPhone 2.0 באוויר.בשירות עצמי, החל מ-2¢/דקה.קראו את הודעת ההשקה

Webhooks

telephony.complete / web.complete

Webhook לא חוסם שנשלח כאשר שיחה מסתיימת, עם תמליל, כתובת URL של ההקלטה ומדדים.

אירוע השלמה מופעל לאחר סיום כל שיחה — טלפוניה נכנסת, טלפוניה יוצאת, שיחת ווב או שיחת בדיקה (הפעלת מיקרופון בבונה). הוא אינו חוסם: השיבו עם כל קוד 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_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: ההקלטה נעצרת ממש לפני שהעברה חמה מתקשרת ליעד, ונשארת כבויה גם אם ההעברה נכשלת והמתקשר חוזר לסוכן. התייחסו לערכים אחרים כמידע בלבד; ייתכן שיתווספו ערכים נוספים. null כאשר recording_stopped_at הוא null
billable_minutesמספרדקות מחויבות, מעוגלות לרבע הדקה הקרוב ביותר (מרווחים של 15 שניות, מינימום 0.25). שיחות ישירות לתא קולי עדיין מדווחות כאן על הדקות הנמדדות בפועל, אך החיוב מוגבל לדקה אחת לפי תעריף התוכנית.
billing_total_centsמספר שלםסנטים של USD
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 כולל גם פלט מאמת פגום או חלקי; הכרעות חלקיות תקפות עדיין חלות, בעוד מועמדים ללא הכרעה תקפה אחת מאופסים ל-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 של התצורה לא החזיר תצורה שניתן להשתמש בה, ולכן השיחה הסתיימה לפני שסוכן ענה. הערך של status הוא failed והשיחה אינה מחויבת
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.
  • שיחות בדיקת מיקרופון ב-Builder מדווחות כ-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

שמרו את תמלול כל שיחה ואת כתובת ה-URL של ההקלטה לצד רשומות הלקוחות שלכם.

ניתוח נתונים

הזרימו תמלולים לצינור עיבוד לצורך מודל נושאים, חילוץ אותות CSAT או ניטור שיעורי העברה.

סקירת איכות

פתחו שיחות בכלי QA לסקירה אנושית, או העבירו אותן דרך מודל ההערכה שלכם.

התראות

הפעילו התראה לנציג אנושי בעת העברה או כשל.


קשור