ThunderPhone 2.0 je tu.Začnite sami, už od 2 ¢/min.Prečítať oznámenie

Webhooks

telephony.complete / web.complete

Neblokujúci webhook doručený po skončení hovoru s prepisom, URL nahrávky a metrikami.

Udalosť dokončenia sa spustí po skončení každého hovoru — prichádzajúcej telefónie, odchádzajúcej telefónie, webového hovoru alebo testovacieho hovoru (relácia mikrofónu v nástroji na vytváranie). Je neblokujúca: odpovedzte ľubovoľným kódom 2xx.

Udalosť sa doručuje oboma spôsobmi:

Údajový obsah požiadavky (doručenia koncovým bodom)

{
  "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"
}
PoleTypPopis
call_idintegerStabilné vo všetkých udalostiach pre tento hovor
agent_idinteger | nullAgent, ktorý spracoval hovor, ak bol priradený
agent_namestring | nullAgent, ktorý spracoval hovor, ak bol priradený
directionstringinbound, outbound, web, test. Historické údajové obsahy môžu obsahovať staršie hodnoty mic alebo widget
from_number, to_numberstringE.164. from_number je doslovná hodnota "web" pre webové a testovacie hovory
origin_domainstringLen pre web/test — pôvod stránky, na ktorej bol hostovaný widget (prázdne pre relácie mikrofónu)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullOdvodené od času začiatku a konca
statusstringcompleted alebo failed
end_reasonstringPozrite si tabuľku nižšie
product, voicestringKonfigurácia agenta platná v čase hovoru
transfer_numberstring | nullNastavené pri presmerovaní hovoru
recording_urlstring | nullČasovo obmedzená podpísaná URL; súbor stiahnite bezodkladne. null, ak nie je k dispozícii záznam hovoru
billable_minutesnumberFakturované minúty zaokrúhlené na najbližšiu štvrtinu minúty (15-sekundové prírastky, minimum 0,25). Hovory priamo do hlasovej schránky tu stále uvádzajú svoje skutočné spoplatnené minúty, ale poplatok je pri sadzbe plánu obmedzený na jednu minútu.
billing_total_centsintegerCenty USD
transcriptsarrayPoložky prepisu jednotlivých ťahov; môžu byť prázdne, keď prepis nie je k dispozícii

Dôvody ukončenia

HodnotaVýznam
user_hangupVzdialená strana zložila ako prvá
ai_hangupAI zámerne ukončila hovor
ai_transferAI presmerovala hovor; transfer_number je nastavené
ai_warm_transferAI dokončila teplé (asistované) presmerovanie
voicemail_hangupBola rozpoznaná hlasová schránka a hovor sa ukončil podľa vašej hodnoty voicemail_action
max_durationHovor dosiahol limit maximálneho trvania
supersededRelácia bola nahradená novšou reláciou
unknownDôvod ukončenia sa nepodarilo určiť

Formát prepisu

Každá položka v transcripts predstavuje jeden konverzačný krok. Roly sú user (reč volajúceho), model (reč agenta a volania nástrojov), tool (výsledky nástrojov) a system (udalosti hovoru, napríklad zmeny jazyka).

[
  {
    "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"] }
    }
  }
]
PoleTypPopis
rolereťazecuser, model, tool alebo system
content_typereťazectext/plain pre reč; application/json pre volania nástrojov, výsledky nástrojov a systémové udalosti
contentreťazec | objektText reči alebo štruktúrovaný objekt uvedený vyššie. Volania nástrojov: {"tool_call": name, "arguments": {…}}. Výsledky nástrojov: {"tool_name": name, "response": {…}}
start_ms, end_mscelé čísloPosuny od začiatku hovoru v ms. Uvádza sa, keď je známe časovanie zvuku
ttfa_mscelé čísloČas do prvého zvuku pre krok model, ak je nameraný
audio_url, audio_urlsreťazec / polePlatnosťou obmedzené podpísané adresy URL pre zvuk kroku, ak sa zaznamenáva pre jednotlivé kroky

Pre úplne štruktúrovanú históriu krokov (so značkami prerušení, výzvami na potvrdenie a nespracovanými pozíciami) použite GET /v1/calls/{call_id}/history.

Rozdiely staršieho payloadu

Staršia obálka webhooku s jednou adresou URL je {"type": "telephony.complete" | "web.complete", "data": {…}} bez event_id a jej data sa líši od payloadu koncového bodu:

Starší payload dokončenia obsahuje aj agent_id a agent_name.

  • Pole krokov sa nachádza v history, nie v transcripts (rovnaká schéma krokov ako vyššie).
  • Sada polí je nespracovaný report na konci hovoru a môže obsahovať ďalšie interné polia nad rámec tabuľky vyššie — s neznámymi poľami zaobchádzajte ako s informatívnymi.
  • Webové hovory (direction: "web") neobsahujú from_number / to_number a pridávajú origin_domain.
  • Hovory testu mikrofónu v nástroji Builder sa v staršej ceste hlásia ako telephony.complete (systém koncového bodu ich mapuje na web.complete).
  • Koordinácia prenosu: keď sa hovor skončí prenosom, starší webhook sa volá synchrónne a môže odpovedať {"transfer_ready": false}, aby signalizoval, že cieľ prenosu nie je pripravený. Akákoľvek iná odpoveď (alebo žiadny starší webhook) umožní pokračovanie prenosu. Doručenia koncovému bodu sa na tento účel nikdy nekonzultujú.

Príklad obslužnej funkcie

Python (FastAPI)
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}
Node.js (Express)
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 });
  },
);

Bežné prípady použitia

Integrácia CRM

Ukladajte prepis každého hovoru a URL nahrávky spolu so záznamami zákazníkov.

Analytika

Odosielajte prepisy do spracovateľského kanála na modelovanie tém, extrakciu signálov CSAT alebo monitorovanie miery presmerovaní.

Kontrola kvality

Otvárajte hovory v nástroji QA na kontrolu človekom alebo ich spúšťajte vo vlastnom modeli hodnotenia.

Upozornenia

Pri presmerovaní alebo zlyhaní upozornite člena tímu.


Súvisiace