ThunderPhone 2.0 je tu.Začnite sami, že od 2 ¢/min.Preberite obvestilo

Webhooks

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:

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"
}
PoljeVrstaOpis
call_idintegerNespremenljiv v vseh dogodkih za ta klic
agent_idinteger | nullAgent, ki je obravnaval klic, kadar je bil dodeljen
agent_namestring | nullAgent, ki je obravnaval klic, kadar je bil dodeljen
directionstringinbound, outbound, web, test. Zgodovinska telesa zahtev lahko vsebujejo zastareli vrednosti mic ali widget
from_number, to_numberstringE.164. from_number je dobesedno "web" za spletne in preskusne klice
origin_domainstringSamo splet/test — izvor strani, ki je gostila gradnik (prazno za seje mic)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullIzpeljano iz začetka/konca
statusstringcompleted ali failed
end_reasonstringOglejte si spodnjo tabelo
product, voicestringKonfiguracija agenta, veljavna ob času klica
variablesobjectVhodne spremenljivke, posnete ob začetku klica
unresolved_variablesarrayImena spremenljivk, na katera se sklicuje konfiguracija klica, vendar ob začetku klica niso bila podana
transfer_numberstring | nullNastavljeno, ko je bil klic preusmerjen
recording_urlstring | nullPodpisani URL s potekom veljavnosti; prenesite ga takoj. null, kadar posnetek ni na voljo
billable_minutesnumberZarač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_centsintegerAmeriški centi
transcriptsarrayVnosi prepisov po obratih; lahko je prazno, kadar prepis ni na voljo
extracted_dataobject | nullRezultat 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

VrednostPomen
user_hangupOddaljena stranka je prva prekinila klic
ai_hangupAI je namerno zaključila klic
ai_transferAI je preusmerila klic; transfer_number je nastavljen
ai_warm_transferAI je dokončala toplo (nadzorovano) preusmeritev
voicemail_hangupZaznana je bila glasovna pošta in klic se je zaključil v skladu z vašim voicemail_action
max_durationKlic je dosegel omejitev najdaljšega trajanja
supersededSejo je zamenjala novejša seja
unknownRazloga 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"] }
    }
  }
]
PoljeVrstaOpis
rolenizuser, model, tool ali system
content_typeniztext/plain za govor; application/json za klice orodij, rezultate orodij in sistemske dogodke
contentniz | objektBesedilo govora ali strukturirani objekt, prikazan zgoraj. Klici orodij: {"tool_call": name, "arguments": {…}}. Rezultati orodij: {"tool_name": name, "response": {…}}
start_ms, end_mscelo številoOdmika od začetka klica v ms. Prisotna, kadar je časovni potek zvoka znan
ttfa_mscelo številoČas do prvega zvoka za obrat model, kadar je izmerjen
audio_url, audio_urlsniz / 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 v transcripts (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") izpustijo from_number / to_number in dodajo origin_domain.
  • Klici za preizkus mikrofona v gradniku se po zastareli poti poročajo kot telephony.complete (sistem končne točke jih preslika v web.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

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 });
  },
);

Pogosti primeri uporabe

Integracija s CRM

Shranite prepis in URL posnetka vsakega klica skupaj z zapisi o svojih strankah.

Analitika

Pretakajte prepise v proces za modeliranje tem, pridobivanje signalov CSAT ali spremljanje deleža preusmeritev.

Pregled kakovosti

Odprite klice v orodju QA za človeški pregled ali jih obdelajte s svojim ocenjevalnim modelom.

Obvestila

Ob preusmeritvi ali napaki obvestite človeškega sodelavca.


Povezano