Open in
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 rādītājiem.
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 nosūtīts pa abiem ceļiem:
- 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 datu slodzi, katrai piegādei piešķirtuevent_id, 30 s noildzi un atkārtotiem mēģinājumiem līdz 24 h. - Mantotā vienas URL tīmekļa aizķere saņem vienu sinhronu mēģinājumu (10 s noildze, bez atkārtotiem mēģinājumiem) ar nedaudz atšķirīgu datu slodzi — skatiet Mantotās datu slodzes atšķirības.
Pieprasījuma slodze (galapunkta 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",
"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"
}| Lauks | Tips | Apraksts |
|---|---|---|
call_id | integer | Nemainīgs visos šī zvana notikumos |
agent_id | integer | null | Balss aģents, kas apstrādāja zvanu, ja tāds bija piešķirts |
agent_name | string | null | Balss aģents, kas apstrādāja zvanu, ja tāds bija piešķirts |
direction | string | inbound, outbound, web, test. Vēsturiskajās slodzēs var būt mantotās vērtības mic vai widget |
from_number, to_number | string | E.164. Tīmekļa un testa zvaniem from_number burtiski ir "web" |
origin_domain | string | Tikai tīmeklim/testam — lapas izcelsme, kurā tika mitināts logrīks (tukša mikrofona sesijām) |
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 |
variables | object | Ievades mainīgie, kuru momentuzņēmums tika izveidots zvana sākumā |
unresolved_variables | array | Mainīgo nosaukumi, uz kuriem atsaucas zvana konfigurācija, bet kuri netika norādīti zvana sākumā |
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 ieraksta artefakts nav pieejams |
billable_minutes | number | Rēķinā iekļautā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ā, arī šeit norāda faktiskās uzskaitītās minūtes, taču maksa ir ierobežota līdz vienai minūtei pēc plāna tarifa. |
billing_total_cents | integer | ASV dolāru centi |
transcripts | array | Transkripcijas ieraksti katram gājienam; var būt tukšs, ja transkripcija nav pieejama |
extracted_data | object | null | Strukturēts ekstrakcijas rezultāts ar status, fields, evidence, verification, field_reasons un schema_version. Katram laukam, kas nav null, ir precīzs strukturāli pārbaudīts citāts (ne vairāk kā 1000 rakstzīmes), kā arī runātāja loma un gājiena indekss; garāki modeļa atgrieztie citāti tiek noraidīti, nevis saīsināti. Pierādījums ir null, ja lauks ir null. verified nozīmē, ka katrs kandidāts saņēma tieši vienu derīgu neatkarīgu vērtējumu. unavailable aptver arī nepareizu vai daļēju verificētāja izvadi; derīgi daļēji vērtējumi joprojām tiek piemēroti, bet kandidāti bez viena derīga vērtējuma tiek iestatīti uz nulli. status ir completed, failed, exhausted, skipped vai skipped_recording_disabled; null, ja balss aģentam nebija ekstrakcijas lauku |
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 tika pabeigts atbilstoši jūsu voicemail_action |
max_duration | Zvans sasniedza maksimālā ilguma ierobežojumu |
superseded | Sesija tika aizstāta ar jaunāku sesiju |
unknown | Beigu iemeslu nevarēja noteikt |
Transkripta formāts
Katrs ieraksts transcripts masīvā ir viena sarunas kārta. 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
maiņa).
[
{
"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 iekļautas, ja ir zināms audio laiks |
ttfa_ms | vesels skaitlis | Laiks līdz pirmajam audio model kārtai, ja tas ir izmērīts |
audio_url, audio_urls | virkne / masīvs | Parakstīti URL ar derīguma termiņu kārtas audio ierakstam, ja audio tiek ierakstīts katrai kārtai |
Pilnībā strukturētai kārtu 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 āķa aploksne ir
{"type": "telephony.complete" | "web.complete", "data": {…}} bez
event_id, un tās data atšķiras no galapunkta slodzes:
Mantotā pabeigšanas slodze ietver arī agent_id un agent_name.
- Kārtu masīvs atrodas sadaļā
history, nevistranscripts(tāda pati kārtu 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ā — nezināmus laukus uzskatiet par informatīviem.
- Tīmekļa zvani (
direction: "web") neiekļaujfrom_number/to_numberun pievienoorigin_domain. - Veidotāja 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, mantotais
tīmekļa āķis tiek izsaukts 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ā tīmekļa āķa neesamība) ļauj pārsūtīšanai turpināties. Galapunktu 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 kopā ar klientu ierakstiem.
Straumējiet transkriptus uz konveijeru tēmu modelēšanai, CSAT signālu iegūšanai vai pārsūtīšanas rādītāja uzraudzībai.
Atveriet zvanus kvalitātes nodrošināšanas rīkā cilvēka veiktai pārbaudei vai palaidiet tos savā novērtēšanas modelī.
Pārsūtīšanas vai kļūmes gadījumā aktivizējiet cilvēku no komandas.