telephony.complete / web.complete
Nebloķējošs tīmekļa aizķeres paziņojums, kas tiek nosūtīts, kad zvans beidzas, ar transkriptu, ieraksta URL un metriku.
Pabeigšanas notikums tiek aktivizēts pēc katra zvana beigām — ienākošās telefonijas, izejošās telefonijas, tīmekļa zvana vai testa zvana (veidotāja mikrofona sesijas). Tas ir nebloķējošs: atbildiet ar jebkuru 2xx.
Notikums tiek piegādāts abos veidos:
- Tīmekļa aizķeru galapunkti saņem
telephony.complete(tālruņa zvani) vaiweb.complete(tīmekļa zvani un veidotāja mikrofona testa zvani) ar tālāk dokumentēto stabilo slodzi, katrai piegādei piešķirtuevent_id, 30 s taimautu un atkārtotiem mēģinājumiem līdz 24 h. - Mantotais viena URL tīmekļa aizķeris saņem vienu sinhronu mēģinājumu (10 s taimauts, bez atkārtotiem mēģinājumiem) ar nedaudz atšķirīgu slodzi — skatiet Mantotās slodzes atšķirības.
Pieprasījuma slodze (galapunktu piegādes)
{
"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"
}| Lauks | Tips | Apraksts |
|---|---|---|
call_id | integer | Nemainīgs visos šī zvana notikumos |
direction | string | inbound, outbound, web, test. Vēsturiskās slodzēs var būt mantotās vērtības mic vai widget |
from_number, to_number | string | E.164. Tīmekļa zvaniem un testa zvaniem from_number ir burtiskā vērtība "web" |
origin_domain | string | Tikai tīmekļa/testa zvaniem — lapas izcelsme, kurā bija iegults logrīks (mikrofona sesijām tukša) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | Atvasināts no sākuma/beigu laika |
status | string | completed vai failed |
end_reason | string | Skatiet tālāk esošo tabulu |
product, voice | string | Zvana laikā spēkā esošā balss aģenta konfigurācija |
transfer_number | string | null | Iestatīts, kad zvans tika pārsūtīts |
recording_url | string | null | Parakstīts URL ar derīguma termiņu; lejupielādējiet nekavējoties. null, ja nav pieejams ieraksta artefakts |
billable_minutes | number | Rēķinam piemērojamās minūtes, noapaļotas līdz tuvākajai ceturtdaļminūtei (15 sekunžu intervāli, minimums 0.25). Zvani, kas uzreiz nonāk balss pastā, šeit joprojām norāda faktiskās uzskaitītās minūtes, taču maksa tiek ierobežota līdz vienai minūtei pēc plāna tarifa. |
billing_total_cents | integer | USD centi |
transcripts | array | Transkripta ieraksti katrai kārtai; var būt tukšs, ja transkripts nav pieejams |
Beigu iemesli
| Vērtība | Nozīme |
|---|---|
user_hangup | Attālā puse pirmā nolika klausuli |
ai_hangup | MI apzināti beidza zvanu |
ai_transfer | MI pārsūtīja zvanu; ir iestatīts transfer_number |
ai_warm_transfer | MI pabeidza silto (ar apstiprinājumu) pārsūtīšanu |
voicemail_hangup | Tika noteikts balss pasts, un zvans beidzās atbilstoši jūsu voicemail_action |
max_duration | Zvans sasniedza maksimālā ilguma ierobežojumu |
superseded | Sesiju aizstāja jaunāka sesija |
unknown | Beigu iemeslu nevarēja noteikt |
Transkripta formāts
Katrs ieraksts transcripts ir viens sarunas gājiens. Lomas ir
user (zvanītāja runa), model (balss aģenta runa un rīku izsaukumi),
tool (rīku rezultāti) un system (zvana notikumi, piemēram, valodas
pārslēgšana).
[
{
"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"] }
}
}
]| Lauks | Tips | Apraksts |
|---|---|---|
role | virkne | user, model, tool vai system |
content_type | virkne | text/plain runai; application/json rīku izsaukumiem, rīku rezultātiem un sistēmas notikumiem |
content | virkne | objekts | Runas teksts vai iepriekš parādītais strukturētais objekts. Rīku izsaukumi: {"tool_call": name, "arguments": {…}}. Rīku rezultāti: {"tool_name": name, "response": {…}} |
start_ms, end_ms | vesels skaitlis | Nobīdes no zvana sākuma milisekundēs. Tiek norādītas, ja ir zināms audio laiks |
ttfa_ms | vesels skaitlis | Laiks līdz pirmajam audio model gājienam, ja tas ir mērīts |
audio_url, audio_urls | virkne / masīvs | Derīguma termiņa ierobežoti parakstīti URL gājiena audio ierakstam, ja audio tiek ierakstīts katram gājienam atsevišķi |
Pilnībā strukturētai gājienu vēsturei (ar pārtraukumu marķieriem,
apstiprinājuma uzvednēm un neapstrādātām pozīcijām) izmantojiet
GET /v1/calls/{call_id}/history.
Mantotās slodzes atšķirības
Mantotās viena URL tīmekļa aizķeres aploksne ir
{"type": "telephony.complete" | "web.complete", "data": {…}} bez
event_id, un tās data atšķiras no galapunkta slodzes:
- Gājienu masīvs atrodas sadaļā
history, nevistranscripts(tāda pati gājienu shēma kā iepriekš). - Lauku kopa ir neapstrādāts zvana beigu pārskats, un tajā var būt papildu iekšējie lauki, kas nav norādīti iepriekšējā tabulā — uzskatiet nezināmos laukus par informatīviem.
- Tīmekļa zvani (
direction: "web") neietverfrom_number/to_numberun pievienoorigin_domain. - Builder mikrofona pārbaudes zvani mantotajā ceļā tiek ziņoti kā
telephony.complete(galapunkta sistēma tos kartē uzweb.complete). - Pārsūtīšanas koordinēšana: ja zvans beidzas ar pārsūtīšanu,
mantotā tīmekļa aizķere tiek izsaukta sinhroni un var atbildēt ar
{"transfer_ready": false}, lai norādītu, ka pārsūtīšanas mērķis nav gatavs. Jebkura cita atbilde (vai mantotās tīmekļa aizķeres neesamība) ļauj pārsūtīšanu turpināt. Galapunkta piegādes šim nolūkam nekad netiek izmantotas.
Piemēra apstrādātājs
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 });
},
);Biežākie lietošanas gadījumi
Saglabājiet katra zvana transkriptu un ieraksta URL līdzās saviem klientu ierakstiem.
Straumējiet transkriptus uz konveijeru tēmu modelēšanai, CSAT signālu ieguvei vai pārsūtīšanas biežuma uzraudzībai.
Atveriet zvanus QA rīkā cilvēka pārbaudei vai apstrādājiet tos ar savu novērtēšanas modeli.
Pārsūtīšanas vai kļūmes gadījumā aktivizējiet cilvēku komandas biedru.