Open in
telephony.complete / web.complete
کال ختم ہونے پر ٹرانسکرپٹ، ریکارڈنگ URL، اور میٹرکس کے ساتھ بھیجا جانے والا نان بلاکنگ ویب ہک۔
ہر کال ختم ہونے کے بعد ایک completion ایونٹ فعال ہوتا ہے — ان باؤنڈ ٹیلی فونی، آؤٹ باؤنڈ ٹیلی فونی، ویب کال، یا ٹیسٹ کال (بلڈر مائیک سیشن)۔ یہ نان بلاکنگ ہے: کسی بھی 2xx کے ساتھ جواب دیں۔
ایونٹ دونوں راستوں پر پہنچایا جاتا ہے:
- ویب ہک اینڈ پوائنٹس کو
telephony.complete(فون کالز) یاweb.complete(ویب کالز اور بلڈر مائیک ٹیسٹ کالز) موصول ہوتا ہے، جس میں ذیل میں دستاویزی مستحکم payload، ہر ڈیلیوری کے لیے ایکevent_id، 30 s کا ٹائم آؤٹ، اور 24 h تک دوبارہ کوششیں شامل ہیں۔ - لیگیسی سنگل-URL ویب ہک کو قدرے مختلف payload کے ساتھ ایک synchronous کوشش موصول ہوتی ہے (10 s ٹائم آؤٹ، کوئی دوبارہ کوشش نہیں) — ملاحظہ کریں لیگیسی payload کے فرق۔
ریکویسٹ پے لوڈ (اینڈ پوائنٹ ڈیلیوریز)
{
"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 | integer | اس کال کے ہر ایونٹ میں یکساں رہتا ہے |
agent_id | integer | null | کال ہینڈل کرنے والا ایجنٹ، جب کوئی ایجنٹ تفویض کیا گیا ہو |
agent_name | string | null | کال ہینڈل کرنے والے ایجنٹ کا نام، جب کوئی ایجنٹ تفویض کیا گیا ہو |
direction | string | inbound، outbound، web، test۔ تاریخی پے لوڈز میں پرانی mic یا widget ویلیوز شامل ہو سکتی ہیں |
from_number, to_number | string | E.164۔ ویب کالز اور ٹیسٹ کالز کے لیے from_number کی لفظی ویلیو "web" ہوتی ہے |
origin_domain | string | صرف ویب/ٹیسٹ — وہ صفحہ اوریجن جس نے widget کو ہوسٹ کیا تھا (mic سیشنز کے لیے خالی) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | آغاز/اختتام سے اخذ کیا گیا |
status | string | completed یا failed |
end_reason | string | نیچے دی گئی جدول دیکھیں |
product, voice | string | کال کے وقت مؤثر ایجنٹ کنفیگ |
variables | object | کال شروع ہونے کے وقت اسنیپ شاٹ کیے گئے ان پٹ ویری ایبلز |
unresolved_variables | array | کال کنفیگ میں حوالہ دیے گئے مگر کال شروع ہونے پر فراہم نہ کیے گئے ویری ایبل نام |
transfer_number | string | null | کال ٹرانسفر ہونے پر سیٹ کیا جاتا ہے |
recording_url | string | null | میعاد ختم ہونے والا دستخط شدہ URL؛ فوراً ڈاؤن لوڈ کریں۔ جب کوئی ریکارڈنگ آرٹیفیکٹ دستیاب نہ ہو تو null |
billable_minutes | number | بل کیے گئے منٹس، قریب ترین چوتھائی منٹ تک راؤنڈ کیے جاتے ہیں (15 سیکنڈ کے اضافے، کم از کم 0.25)۔ براہِ راست وائس میل پر جانے والی کالز بھی یہاں اپنے اصل میٹر کیے گئے منٹس رپورٹ کرتی ہیں، لیکن چارج پلان ریٹ پر ایک منٹ تک محدود ہوتا ہے۔ |
billing_total_cents | integer | امریکی ڈالر سینٹس |
transcripts | array | ہر ٹرن کے ٹرانسکرپٹ اندراجات؛ جب ٹرانسکرپٹ دستیاب نہ ہو تو خالی ہو سکتا ہے |
extracted_data | object | null | status، fields، evidence، verification، field_reasons، اور schema_version کے ساتھ ساختہ استخراجی نتیجہ۔ ہر غیر null فیلڈ میں عین ساختی طور پر جانچا گیا اقتباس (زیادہ سے زیادہ 1,000 حروف)، نیز اس کا اسپیکر رول اور ٹرن انڈیکس شامل ہوتا ہے؛ ماڈل سے واپس آنے والے طویل اقتباسات کو مختصر کرنے کے بجائے مسترد کر دیا جاتا ہے۔ جب فیلڈ null ہو تو evidence بھی null ہوتا ہے۔ verified کا مطلب ہے کہ ہر امیدوار کو عین ایک درست آزاد فیصلہ موصول ہوا۔ unavailable میں خراب یا جزوی ویریفائر آؤٹ پٹ بھی شامل ہے؛ درست جزوی فیصلے پھر بھی لاگو ہوتے ہیں، جبکہ جن امیدواروں کے لیے ایک درست فیصلہ نہ ہو انہیں null کر دیا جاتا ہے۔ 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 (کال کے واقعات، جیسے زبان کی
تبدیلیاں) ہیں۔
[
{
"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 | سٹرنگ / ارے | باری کی آڈیو کے لیے میعاد ختم ہونے والے دستخط شدہ URLs، جب ہر باری کے لیے ریکارڈ کی گئی ہو |
مکمل ساختہ باری ہسٹری کے لیے (جس میں مداخلت کے نشانات،
تصدیقی پرامپٹس، اور خام پوزیشنز شامل ہیں)،
GET /v1/calls/{call_id}/history استعمال کریں۔
پرانے payload کے اختلافات
پرانا سنگل-URL webhook لفافہ
{"type": "telephony.complete" | "web.complete", "data": {…}} ہے، جس میں
کوئی event_id نہیں ہوتا، اور اس کا data اینڈ پوائنٹ payload سے مختلف ہے:
پرانے تکمیلی payload میں agent_id اور agent_name بھی شامل ہوتے ہیں۔
- باریوں کا ارے
transcriptsکے بجائےhistoryکے تحت ہوتا ہے (اوپر جیسا ہی باری اسکیما)۔ - فیلڈ سیٹ کال کے اختتام کی خام رپورٹ ہوتا ہے اور اس میں اوپر دیے گئے جدول سے آگے اضافی اندرونی فیلڈز شامل ہو سکتے ہیں — نامعلوم فیلڈز کو معلوماتی سمجھیں۔
- ویب کالز (
direction: "web") میںfrom_number/to_numberشامل نہیں ہوتے اورorigin_domainشامل ہوتا ہے۔ - Builder مائیک ٹیسٹ کالز پرانے پاتھ میں
telephony.completeکے طور پر رپورٹ ہوتی ہیں (اینڈ پوائنٹ سسٹم انہیںweb.completeپر میپ کرتا ہے)۔ - ٹرانسفر کوآرڈینیشن: جب کوئی کال ٹرانسفر میں ختم ہوتی ہے، تو
پرانا webhook ہم وقت طریقے سے کال کیا جاتا ہے اور یہ اشارہ دینے کے لیے
{"transfer_ready": false}کا جواب دے سکتا ہے کہ ہینڈ آف کا ہدف تیار نہیں ہے۔ کوئی بھی دوسرا جواب (یا پرانا webhook نہ ہونا) ٹرانسفر کو جاری رکھنے دیتا ہے۔ اس کے لیے اینڈ پوائنٹ ڈیلیوریز سے کبھی رجوع نہیں کیا جاتا۔
ہینڈلر کی مثال
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}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 });
},
);عام استعمال کے معاملات
ہر کال کی ٹرانسکرپٹ اور ریکارڈنگ URL کو اپنے صارف کے ریکارڈز کے ساتھ محفوظ کریں۔
موضوعاتی ماڈلنگ، CSAT سگنل نکالنے، یا ٹرانسفر کی شرح کی نگرانی کے لیے ٹرانسکرپٹس کو پائپ لائن میں اسٹریم کریں۔
انسانی جائزے کے لیے کالز کو QA ٹول میں کھولیں، یا انہیں اپنے تشخیصی ماڈل کے ذریعے چلائیں۔
ٹرانسفر یا ناکامی پر انسانی ٹیم کے رکن کو متحرک کریں۔