Open in
telephony.complete / web.complete
Neblokirajoči webhook, poslan ob koncu klica, s prepisom, URL-jem posnetka in meritvami.
Dogodek dokončanja se sproži po koncu vsakega klica — dohodne telefonije, odhodne telefonije, spletnega klica ali preizkusnega klica (seja mikrofona v gradniku). Je neblokirajoč: odgovorite s katerim koli 2xx.
Dogodek je dostavljen po obeh poteh:
- Končne točke webhookov prejmejo
telephony.complete(telefonski klici) aliweb.complete(spletni klici in preizkusni klici z mikrofonom v gradniku) s spodaj dokumentiranim stabilnim koristnim tovorom,event_idza vsako dostavo, časovno omejitvijo 30 s in ponovnimi poskusi do 24 h. - Zastareli webhook z enim URL-jem prejme en sinhroni poskus (časovna omejitev 10 s, brez ponovnih poskusov) z nekoliko drugačnim koristnim tovorom — glejte Razlike v zastarelem koristnem tovoru.
Telo zahteve (dostave na končno točko)
{
"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"
}| Polje | Vrsta | Opis |
|---|---|---|
call_id | integer | Nespremenljiv v vseh dogodkih za ta klic |
agent_id | integer | null | Agent, ki je obravnaval klic, kadar je bil dodeljen |
agent_name | string | null | Agent, ki je obravnaval klic, kadar je bil dodeljen |
direction | string | inbound, outbound, web, test. Zgodovinska telesa zahtev lahko vsebujejo zastareli vrednosti mic ali widget |
from_number, to_number | string | E.164. from_number je dobesedno "web" za spletne in preskusne klice |
origin_domain | string | Samo splet/test — izvor strani, ki je gostila gradnik (prazno za seje mic) |
start_time, end_time | timestamp | ISO 8601 UTC |
duration_seconds | integer | null | Izpeljano iz začetka/konca |
status | string | completed ali failed |
end_reason | string | Oglejte si spodnjo tabelo |
product, voice | string | Konfiguracija agenta, veljavna ob času klica |
variables | object | Vhodne spremenljivke, posnete ob začetku klica |
unresolved_variables | array | Imena spremenljivk, na katera se sklicuje konfiguracija klica, vendar ob začetku klica niso bila podana |
transfer_number | string | null | Nastavljeno, ko je bil klic preusmerjen |
recording_url | string | null | Podpisani URL s potekom veljavnosti; prenesite ga takoj. null, kadar posnetek ni na voljo |
billable_minutes | number | Zaračunane minute, zaokrožene na najbližjo četrtino minute (koraki po 15 sekund, najmanj 0,25). Klici, ki so preusmerjeni neposredno v glasovno pošto, tukaj še vedno poročajo o dejanskih izmerjenih minutah, vendar je strošek omejen na eno minuto po tarifi paketa. |
billing_total_cents | integer | Ameriški centi |
transcripts | array | Vnosi prepisov po obratih; lahko je prazno, kadar prepis ni na voljo |
extracted_data | object | null | Rezultat strukturiranega izvlečenja s polji status, fields, evidence, verification, field_reasons in schema_version. Vsako polje, ki ni null, vsebuje natančen strukturno preverjen citat (največ 1.000 znakov) ter vlogo govorca in indeks obrata; daljši citati, ki jih vrne model, so zavrnjeni namesto skrajšani. Dokaz je null, kadar je polje null. verified pomeni, da je vsak kandidat prejel natanko eno veljavno neodvisno presojo. unavailable zajema tudi nepravilno oblikovan ali delni izhod preverjevalnika; veljavne delne presoje se še vedno upoštevajo, medtem ko se kandidati brez ene veljavne presoje nastavijo na null. status je completed, failed, exhausted, skipped ali skipped_recording_disabled; null, kadar agent ni imel polj za izvlečenje |
Razlogi za zaključek
| Vrednost | Pomen |
|---|---|
user_hangup | Oddaljena stranka je prva prekinila klic |
ai_hangup | AI je namerno zaključila klic |
ai_transfer | AI je preusmerila klic; transfer_number je nastavljen |
ai_warm_transfer | AI je dokončala toplo (nadzorovano) preusmeritev |
voicemail_hangup | Zaznana je bila glasovna pošta in klic se je zaključil v skladu z vašim voicemail_action |
max_duration | Klic je dosegel omejitev najdaljšega trajanja |
superseded | Sejo je zamenjala novejša seja |
unknown | Razloga za zaključek ni bilo mogoče določiti |
Oblika prepisa
Vsak vnos v transcripts predstavlja en pogovorni obrat. Vloge so
user (govor klicatelja), model (govor agenta in klici orodij),
tool (rezultati orodij) in system (dogodki klica, kot so preklopi
jezika).
[
{
"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"] }
}
}
]| Polje | Vrsta | Opis |
|---|---|---|
role | niz | user, model, tool ali system |
content_type | niz | text/plain za govor; application/json za klice orodij, rezultate orodij in sistemske dogodke |
content | niz | objekt | Besedilo govora ali strukturirani objekt, prikazan zgoraj. Klici orodij: {"tool_call": name, "arguments": {…}}. Rezultati orodij: {"tool_name": name, "response": {…}} |
start_ms, end_ms | celo število | Odmika od začetka klica v ms. Prisotna, kadar je časovni potek zvoka znan |
ttfa_ms | celo število | Čas do prvega zvoka za obrat model, kadar je izmerjen |
audio_url, audio_urls | niz / polje | Časovno omejeni podpisani URL-ji za zvok obrata, kadar se zvok snema za posamezni obrat |
Za celotno strukturirano zgodovino obratov (z oznakami prekinitev,
potrditvenimi pozivi in neobdelanimi položaji) uporabite
GET /v1/calls/{call_id}/history.
Razlike v zastarelem koristnem tovoru
Zastareli ovoj webhooka z enim URL-jem je
{"type": "telephony.complete" | "web.complete", "data": {…}} brez
event_id, njegov data pa se razlikuje od koristnega tovora končne točke:
Zastareli koristni tovor ob zaključku vključuje tudi agent_id in agent_name.
- Polje obratov je v
history, ne vtranscripts(ista shema obratov kot zgoraj). - Nabor polj je neobdelano poročilo ob koncu klica in lahko vključuje dodatna interna polja poleg zgornje tabele — neznana polja obravnavajte kot informativna.
- Spletni klici (
direction: "web") izpustijofrom_number/to_numberin dodajoorigin_domain. - Klici za preizkus mikrofona v gradniku se po zastareli poti poročajo kot
telephony.complete(sistem končne točke jih preslika vweb.complete). - Usklajevanje prenosa: ko se klic konča s prenosom, se zastareli webhook
pokliče sinhrono in lahko odgovori z
{"transfer_ready": false}, kar sporoča, da cilj prenosa še ni pripravljen. Kateri koli drug odgovor (ali odsotnost zastarelega webhooka) omogoči nadaljevanje prenosa. Dostave končne točke se za to nikoli ne upoštevajo.
Primer obdelovalnika
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 });
},
);Pogosti primeri uporabe
Shranite prepis in URL posnetka vsakega klica skupaj z zapisi o svojih strankah.
Pretakajte prepise v proces za modeliranje tem, pridobivanje signalov CSAT ali spremljanje deleža preusmeritev.
Odprite klice v orodju QA za človeški pregled ali jih obdelajte s svojim ocenjevalnim modelom.
Ob preusmeritvi ali napaki obvestite človeškega sodelavca.