Open in
telephony.complete / web.complete
Neblokuojantis žiniatinklio kabliukas, pateikiamas pasibaigus skambučiui, su transkriptu, įrašo URL ir metrika.
Užbaigimo įvykis suaktyvinamas pasibaigus kiekvienam skambučiui – įeinančios telefonijos, išeinančios telefonijos, žiniatinklio skambučiui arba bandomajam skambučiui (kūrimo priemonės mikrofono seansui). Jis yra neblokuojantis: atsakykite bet kokiu 2xx kodu.
Įvykis pristatomas abiem keliais:
- Webhook galiniai taškai gauna
telephony.complete(telefono skambučiams) arbaweb.complete(žiniatinklio skambučiams ir kūrimo priemonės mikrofono bandomiesiems skambučiams) su toliau dokumentuotu stabiliu paketu, kiekvienam pristatymui skirtuevent_id, 30 s skirtuoju laiku ir pakartotiniais bandymais iki 24 val.. - Senasis vieno URL webhook gauna vieną sinchroninį bandymą (10 s skirtasis laikas, be pakartotinių bandymų) su šiek tiek kitokiu paketu – žr. Senesnio paketo skirtumai.
Užklausos paketas (galinių taškų pristatymai)
{
"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"
}| Laukas | Tipas | Aprašymas |
|---|---|---|
call_id | integer | Stabilus visuose šio skambučio įvykiuose |
agent_id | integer | null | Skambutį aptarnavęs agentas, jei jis buvo priskirtas |
agent_name | string | null | Skambutį aptarnavusio agento pavadinimas, jei jis buvo priskirtas |
direction | string | inbound, outbound, web, test. Istoriniuose paketuose gali būti senesnės mic arba widget reikšmės |
from_number, to_number | string | E.164. Žiniatinklio ir bandomiesiems skambučiams from_number yra pažodinė reikšmė "web" |
origin_domain | string | Tik žiniatinklio ir bandomiesiems skambučiams – puslapio, kuriame buvo valdiklis, kilmė (mikrofono seansams tuščia) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | Apskaičiuojama pagal pradžios ir pabaigos laiką |
status | string | completed arba failed |
end_reason | string | Žr. toliau pateiktą lentelę |
product, voice | string | Skambučio metu taikyta agento konfigūracija |
transfer_number | string | null | Nustatoma, kai skambutis buvo peradresuotas |
recording_url | string | null | Galiojanti pasirašyta URL; atsisiųskite nedelsdami. null, kai nėra įrašo artefakto |
billable_minutes | number | Apmokestinamos minutės, suapvalintos iki artimiausio ketvirčio minutės (15 sekundžių intervalai, mažiausiai 0,25). Tiesiai į balso paštą nukreipti skambučiai čia vis tiek nurodo faktines apskaitytas minutes, tačiau mokestis ribojamas iki vienos minutės pagal plano tarifą. |
billing_total_cents | integer | JAV dolerių centai |
transcripts | array | Kiekvieno pokalbio posūkio transkripto įrašai; gali būti tuščias, kai transkriptas nepasiekiamas |
Pabaigos priežastys
| Reikšmė | Reikšmė |
|---|---|
user_hangup | Kita šalis pirmoji padėjo ragelį |
ai_hangup | AI sąmoningai baigė skambutį |
ai_transfer | AI peradresavo skambutį; nustatytas transfer_number |
ai_warm_transfer | AI užbaigė šiltąjį peradresavimą su dalyvavimu |
voicemail_hangup | Aptiktas balso paštas ir skambutis baigtas pagal jūsų voicemail_action |
max_duration | Skambutis pasiekė maksimalią trukmės ribą |
superseded | Seansą pakeitė naujesnis seansas |
unknown | Nepavyko nustatyti pabaigos priežasties |
Transkripto formatas
Kiekvienas transcripts įrašas yra vienas pokalbio ėjimas. Vaidmenys yra
user (skambinančiojo kalba), model (agento kalba ir įrankių iškvietimai),
tool (įrankių rezultatai) ir system (skambučio įvykiai, pvz., kalbos
perjungimai).
[
{
"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"] }
}
}
]| Laukas | Tipas | Aprašymas |
|---|---|---|
role | eilutė | user, model, tool arba system |
content_type | eilutė | text/plain kalbai; application/json įrankių iškvietimams, įrankių rezultatams ir sistemos įvykiams |
content | eilutė | objektas | Kalbos tekstas arba pirmiau parodytas struktūrizuotas objektas. Įrankių iškvietimai: {"tool_call": name, "arguments": {…}}. Įrankių rezultatai: {"tool_name": name, "response": {…}} |
start_ms, end_ms | sveikasis skaičius | Poslinkiai nuo skambučio pradžios, ms. Pateikiami, kai žinomas garso laikas |
ttfa_ms | sveikasis skaičius | Laikas iki pirmojo garso model ėjimui, kai išmatuotas |
audio_url, audio_urls | eilutė / masyvas | Baigiančio galioti pasirašyti URL ėjimo garso įrašui, kai įrašoma kiekvienam ėjimui |
Visai struktūrizuotai ėjimų istorijai (su pertraukimo žymenimis,
patvirtinimo raginimais ir neapdorotomis pozicijomis) naudokite
GET /v1/calls/{call_id}/history.
Ankstesnės versijos naudingosios apkrovos skirtumai
Ankstesnės versijos vieno URL žiniatinklio kablio apvalkalas yra
{"type": "telephony.complete" | "web.complete", "data": {…}} be
event_id, o jo data skiriasi nuo galinio taško naudingosios apkrovos:
Ankstesnės versijos užbaigimo naudingoji apkrova taip pat turi agent_id ir agent_name.
- Ėjimų masyvas yra
history, o netranscripts(ta pati ėjimo schema kaip pirmiau). - Laukų rinkinys yra neapdorota skambučio pabaigos ataskaita ir gali apimti papildomus vidinius laukus, kurių nėra pirmiau pateiktoje lentelėje — nežinomus laukus laikykite informaciniais.
- Žiniatinklio skambučiai (
direction: "web") nepateikiafrom_number/to_numberir pridedaorigin_domain. - Konstruktoriaus mikrofono bandomieji skambučiai ankstesnės versijos
kelyje pateikiami kaip
telephony.complete(galinio taško sistema juos susieja suweb.complete). - Perdavimo koordinavimas: kai skambutis baigiasi perdavimu, ankstesnės
versijos žiniatinklio kablys iškviečiamas sinchroniškai ir gali atsakyti
{"transfer_ready": false}, kad nurodytų, jog perdavimo paskirties vieta nėra parengta. Bet koks kitas atsakymas (arba ankstesnės versijos žiniatinklio kablio nebuvimas) leidžia tęsti perdavimą. Galinio taško pristatymai tam niekada nenaudojami.
Pavyzdinė apdorojimo funkcija
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 });
},
);Dažniausi naudojimo atvejai
Išsaugokite kiekvieno skambučio transkriptą ir įrašo URL kartu su klientų įrašais.
Siųskite transkriptus į duomenų srautą temų modeliavimui, CSAT signalų išskyrimui arba perdavimo rodiklio stebėjimui.
Atidarykite skambučius QA įrankyje, kad juos peržiūrėtų žmonės, arba apdorokite juos savo vertinimo modeliu.
Perduodant skambutį arba įvykus klaidai praneškite komandos nariui.