telephony.complete / web.complete
कॉल समाप्त होने पर ट्रांसक्रिप्ट, रिकॉर्डिंग URL और मेट्रिक्स के साथ डिलीवर किया जाने वाला नॉन-ब्लॉकिंग वेबहुक।
हर कॉल समाप्त होने के बाद एक कंप्लीशन इवेंट ट्रिगर होता है — इनबाउंड टेलीफोनी, आउटबाउंड टेलीफोनी, वेब कॉल या टेस्ट कॉल (बिल्डर माइक सेशन)। यह नॉन-ब्लॉकिंग है: किसी भी 2xx के साथ रिस्पॉन्ड करें।
इवेंट दोनों पाथ पर डिलीवर होता है:
- Webhook एंडपॉइंट्स को
telephony.complete(फोन कॉल) याweb.complete(वेब कॉल और बिल्डर माइक टेस्ट कॉल) मिलता है, जिसमें नीचे डॉक्यूमेंट किया गया स्टेबल पेलोड, प्रत्येक डिलीवरी काevent_id, 30 s टाइमआउट और 24 h तक रिट्राई शामिल होते हैं। - लेगसी सिंगल-URL वेबहुक को थोड़ा अलग पेलोड के साथ एक सिंक्रोनस प्रयास मिलता है (10 s टाइमआउट, कोई रिट्राई नहीं) — देखें लेगसी पेलोड अंतर।
रिक्वेस्ट पेलोड (एंडपॉइंट डिलीवरी)
{
"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",
"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,
"voice": "john"
},
"event_id": "6a7b8c9d-0e1f-4a2b-8c3d-4e5f6a7b8c9d",
"type": "telephony.complete"
}| फ़ील्ड | टाइप | विवरण |
|---|---|---|
call_id | इंटीजर | इस कॉल के प्रत्येक इवेंट में स्टेबल रहता है |
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 | स्ट्रिंग | कॉल के समय प्रभावी एजेंट कॉन्फ़िग |
transfer_number | स्ट्रिंग | null | कॉल ट्रांसफर होने पर सेट होता है |
recording_url | स्ट्रिंग | null | एक्सपायर होने वाला साइन किया गया URL; तुरंत डाउनलोड करें। रिकॉर्डिंग आर्टिफैक्ट उपलब्ध न होने पर null |
billable_minutes | नंबर | बिल किए गए मिनट, निकटतम क्वार्टर मिनट तक राउंड किए गए (15-सेकंड इंक्रीमेंट, न्यूनतम 0.25)। सीधे वॉइसमेल पर जाने वाली कॉल यहां भी अपने वास्तविक मीटर्ड मिनट रिपोर्ट करती हैं, लेकिन प्लान रेट पर शुल्क एक मिनट तक सीमित होता है। |
billing_total_cents | इंटीजर | USD सेंट |
transcripts | ऐरे | प्रत्येक टर्न की ट्रांसक्रिप्ट एंट्री; ट्रांसक्रिप्ट उपलब्ध न होने पर खाली हो सकता है |
समाप्ति के कारण
| वैल्यू | अर्थ |
|---|---|
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 | स्ट्रिंग / ऐरे | टर्न के ऑडियो के लिए एक्सपायर होने वाले साइन किए गए URL, जब प्रति-टर्न रिकॉर्ड किए गए हों |
पूरी तरह स्ट्रक्चर्ड टर्न हिस्ट्री (इंटरप्शन मार्कर,
ack-प्रॉम्प्ट और रॉ पोज़िशन के साथ) के लिए,
GET /v1/calls/{call_id}/history का उपयोग करें।
लेगेसी पेलोड अंतर
लेगेसी सिंगल-URL वेबहुक एनवेलप है
{"type": "telephony.complete" | "web.complete", "data": {…}}, जिसमें
कोई event_id नहीं होता, और इसका data एंडपॉइंट पेलोड से अलग होता है:
- टर्न ऐरे
transcriptsनहीं, बल्किhistoryके अंतर्गत होता है (ऊपर जैसा ही टर्न स्कीमा)। - फ़ील्ड सेट रॉ एंड-ऑफ़-कॉल रिपोर्ट होता है और इसमें ऊपर दी गई टेबल से परे अतिरिक्त इंटरनल फ़ील्ड शामिल हो सकते हैं — अज्ञात फ़ील्ड को जानकारी के रूप में मानें।
- वेब कॉल (
direction: "web")from_number/to_numberको छोड़ती हैं औरorigin_domainजोड़ती हैं। - बिल्डर माइक टेस्ट कॉल लेगेसी पाथ पर
telephony.completeके रूप में रिपोर्ट होती हैं (एंडपॉइंट सिस्टम उन्हेंweb.completeपर मैप करता है)। - ट्रांसफर कोऑर्डिनेशन: जब कोई कॉल ट्रांसफर में समाप्त होती है, तो
लेगेसी वेबहुक को सिंक्रोनस रूप से कॉल किया जाता है और हैंडऑफ टारगेट तैयार नहीं होने का
संकेत देने के लिए वह
{"transfer_ready": false}उत्तर दे सकता है। कोई अन्य उत्तर (या कोई लेगेसी वेबहुक न होना) ट्रांसफर को आगे बढ़ने देता है। इसके लिए एंडपॉइंट डिलीवरी से कभी परामर्श नहीं किया जाता।
उदाहरण हैंडलर
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 टूल में खोलें, या उन्हें अपने मूल्यांकन मॉडल से चलाएं।
ट्रांसफर / विफलता पर मानव टीममेट को ट्रिगर करें।