Open in
telephony.complete / web.complete
કૉલ સમાપ્ત થાય ત્યારે ટ્રાન્સક્રિપ્ટ, રેકોર્ડિંગ URL અને મેટ્રિક્સ સાથે મોકલવામાં આવતો નૉન-બ્લોકિંગ webhook.
દરેક કૉલ સમાપ્ત થયા પછી પૂર્ણતા ઇવેન્ટ ટ્રિગર થાય છે — ઇનબાઉન્ડ ટેલિફોની, આઉટબાઉન્ડ ટેલિફોની, વેબ કૉલ અથવા ટેસ્ટ કૉલ (બિલ્ડર માઇક સેશન). તે બિન-અવરોધક છે: કોઈપણ 2xx સાથે પ્રતિસાદ આપો.
ઇવેન્ટ બંને પાથ પર ડિલિવર થાય છે:
- વેબહૂક એન્ડપૉઇન્ટ્સ ને
telephony.complete(ફોન કૉલ્સ) અથવાweb.complete(વેબ કૉલ્સ અને બિલ્ડર માઇક ટેસ્ટ કૉલ્સ) નીચે દસ્તાવેજીકૃત સ્થિર પેલોડ સાથે, દરેક ડિલિવરી માટેevent_id, 30 સેકન્ડનો ટાઇમઆઉટ અને 24 કલાક સુધી પુનઃપ્રયાસો મળે છે. - લેગસી સિંગલ-URL વેબહૂક ને થોડા અલગ પેલોડ સાથે એક સમકાલીન પ્રયાસ મળે છે (10 સેકન્ડનો ટાઇમઆઉટ, પુનઃપ્રયાસ નહીં) — જુઓ લેગસી પેલોડ તફાવતો.
વિનંતી પેલોડ (endpoint ડિલિવરીઝ)
{
"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 હોસ્ટ કરનાર પેજનું origin (mic સત્રો માટે ખાલી) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | શરૂઆત/અંત પરથી મેળવેલ |
status | string | completed અથવા failed |
end_reason | string | નીચેનું ટેબલ જુઓ |
product, voice | string | કૉલ સમયે અમલમાં રહેલું એજન્ટ config |
variables | object | કૉલ શરૂ થયો ત્યારે snapshot લેવાયેલા ઇનપુટ variables |
unresolved_variables | array | કૉલ config દ્વારા સંદર્ભિત, પરંતુ કૉલ શરૂ સમયે પૂરા પાડવામાં ન આવેલા variable નામો |
transfer_number | string | null | કૉલ ટ્રાન્સફર કરવામાં આવ્યો હોય ત્યારે સેટ થાય છે |
recording_url | string | null | સમયસમાપ્ત થતી signed URL; તરત ડાઉનલોડ કરો. જ્યારે કોઈ રેકોર્ડિંગ artifact ઉપલબ્ધ ન હોય ત્યારે null |
billable_minutes | number | બિલ કરાયેલા મિનિટ, નજીકના ક્વાર્ટર મિનિટ સુધી રાઉન્ડ કરેલા (15-સેકન્ડના વધારા, ન્યૂનતમ 0.25). voicemail પર સીધા જતા કૉલ પણ અહીં તેમના વાસ્તવિક metered મિનિટ દર્શાવે છે, પરંતુ ચાર્જ plan rate પર એક મિનિટ સુધી મર્યાદિત હોય છે. |
billing_total_cents | integer | USD સેન્ટ |
transcripts | array | દરેક turn માટેની transcript entries; transcript ઉપલબ્ધ ન હોય ત્યારે ખાલી હોઈ શકે છે |
extracted_data | object | null | status, fields, evidence, verification, field_reasons, અને schema_version સાથેનું સંરચિત extraction પરિણામ. દરેક non-null ફીલ્ડમાં ચોક્કસ structurally checked quote (વધુમાં વધુ 1,000 અક્ષરો), તેની speaker role અને turn index સાથે હોય છે; મોડેલ દ્વારા પરત અપાયેલા લાંબા quote ટૂંકાવવાના બદલે નકારવામાં આવે છે. ફીલ્ડ null હોય ત્યારે evidence null હોય છે. verified નો અર્થ છે કે દરેક candidate ને બરાબર એક માન્ય સ્વતંત્ર verdict મળ્યો છે. unavailable માં ખોટા બંધારણવાળા અથવા આંશિક verifier output નો પણ સમાવેશ થાય છે; માન્ય આંશિક verdict હજુ પણ લાગુ પડે છે, જ્યારે એક પણ માન્ય verdict વિનાના candidate ને null કરવામાં આવે છે. status completed, failed, exhausted, skipped, અથવા skipped_recording_disabled છે; જ્યારે એજન્ટ પાસે કોઈ extraction fields ન હોય ત્યારે null |
સમાપ્તિના કારણો
| મૂલ્ય | અર્થ |
|---|---|
user_hangup | રિમોટ પક્ષે પહેલાં કૉલ બંધ કર્યો |
ai_hangup | AI એ ઇરાદાપૂર્વક કૉલ સમાપ્ત કર્યો |
ai_transfer | AI એ કૉલ ટ્રાન્સફર કર્યો; transfer_number સેટ છે |
ai_warm_transfer | AI એ warm (attended) ટ્રાન્સફર પૂર્ણ કર્યો |
voicemail_hangup | voicemail શોધાયું અને તમારા 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 | string | user, model, tool, અથવા system |
content_type | string | બોલાણાં માટે text/plain; ટૂલ કૉલ્સ, ટૂલ પરિણામો અને સિસ્ટમ ઇવેન્ટ્સ માટે application/json |
content | string | object | બોલાણાનો ટેક્સ્ટ, અથવા ઉપર દર્શાવેલ સ્ટ્રક્ચર્ડ ઑબ્જેક્ટ. ટૂલ કૉલ્સ: {"tool_call": name, "arguments": {…}}. ટૂલ પરિણામો: {"tool_name": name, "response": {…}} |
start_ms, end_ms | integer | કૉલ શરૂ થવાથી ઑફસેટ, ms. ઑડિયો ટાઇમિંગ જાણીતું હોય ત્યારે હાજર હોય છે |
ttfa_ms | integer | માપવામાં આવ્યું હોય ત્યારે model વારાની પ્રથમ ઑડિયો સુધીનો સમય |
audio_url, audio_urls | string / array | દરેક વાર માટે રેકોર્ડ કરાયેલ ઑડિયોની સમય-સમાપ્તિ ધરાવતી સહીવાળી URLs |
સંપૂર્ણ સ્ટ્રક્ચર્ડ વારા ઇતિહાસ માટે (ઇન્ટરપ્શન માર્કર્સ,
ack-prompts અને રૉ પોઝિશન્સ સાથે), ઉપયોગ કરો
GET /v1/calls/{call_id}/history.
લેગસી પેલોડ તફાવતો
લેગસી સિંગલ-URL વેબહૂક એન્વલપ છે
{"type": "telephony.complete" | "web.complete", "data": {…}}, જેમાં
કોઈ event_id નથી, અને તેનું data એન્ડપોઇન્ટ પેલોડથી અલગ છે:
લેગસી પૂર્ણતા પેલોડમાં agent_id અને agent_name પણ હોય છે.
- વારા એરે
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 ટૂલમાં કૉલ્સ ખોલો અથવા તેમને તમારા પોતાના મૂલ્યાંકન મોડલ દ્વારા ચલાવો.
ટ્રાન્સફર / નિષ્ફળતા પર માનવ ટીમસાથીને ટ્રિગર કરો.