ThunderPhone 2.0 je stigao.Postavite sve sami, već od 2 ¢/min.Pročitajte objavu

Webhooks

telephony.complete / web.complete

Neblokirajući webhook koji se isporučuje kada poziv završi, s transkriptom, URL-om snimke i metrikama.

Događaj dovršetka pokreće se nakon završetka svakog poziva — dolazne telefonije, odlazne telefonije, web-poziva ili testnog poziva (sesija mikrofona u alatu za izradu). On je neblokirajući: odgovorite bilo kojim statusom 2xx.

Događaj se isporučuje na oba načina:

Tijelo zahtjeva (isporuke krajnjoj točki)

{
  "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_idintegerStabilan u svim događajima za ovaj poziv
agent_idinteger | nullAgent koji je obradio poziv, ako je dodijeljen
agent_namestring | nullAgent koji je obradio poziv, ako je dodijeljen
directionstringinbound, outbound, web, test. Povijesna tijela podataka mogu sadržavati zastarjele vrijednosti mic ili widget
from_number, to_numberstringE.164. from_number je doslovna vrijednost "web" za web-pozive i testne pozive
origin_domainstringSamo za web/test — ishodište stranice na kojoj je smješten widget (prazno za mikrofonske sesije)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullIzvedeno iz vremena početka/završetka
statusstringcompleted ili failed
end_reasonstringPogledajte tablicu u nastavku
product, voicestringKonfiguracija agenta aktivna u trenutku poziva
variablesobjectUlazne varijable zabilježene pri početku poziva
unresolved_variablesarrayNazivi varijabli na koje se poziva konfiguracija poziva, ali nisu zadane pri početku poziva
transfer_numberstring | nullPostavljeno kada je poziv preusmjeren
recording_urlstring | nullPotpisani URL s istekom; preuzmite odmah. null kada artefakt snimke nije dostupan
billable_minutesnumberNaplativi minuti, zaokruženi na najbližu četvrtinu minute (koraci od 15 sekundi, najmanje 0.25). Pozivi koji idu izravno na govornu poštu i dalje ovdje prijavljuju stvarne mjerene minute, ali se naknada ograničava na jednu minutu po cijeni plana.
billing_total_centsintegerAmerički centi
transcriptsarrayUnosi prijepisa po izmjeni; mogu biti prazni kada prijepis nije dostupan
extracted_dataobject | nullRezultat strukturiranog izdvajanja sa stavkama status, fields, evidence, verification, field_reasons i schema_version. Svako polje koje nije null sadrži točan strukturno provjeren citat (najviše 1.000 znakova), kao i ulogu govornika i indeks izmjene; dulji citati koje vrati model odbacuju se umjesto da se skraćuju. Dokaz je null kada je polje null. verified znači da je svaki kandidat dobio točno jednu valjanu neovisnu presudu. unavailable također obuhvaća neispravan ili djelomičan izlaz provjeravatelja; valjane djelomične presude i dalje se primjenjuju, dok se kandidati bez jedne valjane presude postavljaju na null. status je completed, failed, exhausted, skipped ili skipped_recording_disabled; null kada agent nije imao polja za izdvajanje

Razlozi završetka

VrijednostZnačenje
user_hangupUdaljena strana prva je prekinula poziv
ai_hangupAI je namjerno završio poziv
ai_transferAI je preusmjerio poziv; transfer_number je postavljen
ai_warm_transferAI je dovršio toplo (nadzirano) preusmjeravanje
voicemail_hangupOtkrivena je govorna pošta, a poziv je završen prema vašem voicemail_action
max_durationPoziv je dosegnuo ograničenje maksimalnog trajanja
supersededSesiju je zamijenila novija sesija
unknownRazlog završetka nije se mogao utvrditi

Format transkripta

Svaki unos u transcripts jedna je razgovorna izmjena. Uloge su user (govor pozivatelja), model (govor agenta i pozivi alata), tool (rezultati alata) i system (događaji poziva, kao što su promjene 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
roleniz znakovauser, model, tool ili system
content_typeniz znakovatext/plain za govor; application/json za pozive alata, rezultate alata i događaje sustava
contentniz znakova | objektTekst govora ili strukturirani objekt prikazan iznad. Pozivi alata: {"tool_call": name, "arguments": {…}}. Rezultati alata: {"tool_name": name, "response": {…}}
start_ms, end_mscijeli brojPomaci od početka poziva, u ms. Prisutno kada je poznato vremensko usklađivanje zvuka
ttfa_mscijeli brojVrijeme do prvog zvuka za izmjenu model, kada je izmjereno
audio_url, audio_urlsniz znakova / poljeIstječući potpisani URL-ovi za zvuk izmjene, kada se snima po izmjeni

Za potpuno strukturiranu povijest izmjena (s oznakama prekida, upitima za potvrdu i izvornim položajima) upotrijebite GET /v1/calls/{call_id}/history.

Razlike u naslijeđenom payloadu

Naslijeđena omotnica webhooka s jednim URL-om jest {"type": "telephony.complete" | "web.complete", "data": {…}} bez event_id, a njezin se data razlikuje od payloada krajnje točke:

Naslijeđeni payload dovršetka također sadrži agent_id i agent_name.

  • Polje izmjena nalazi se pod history, a ne pod transcripts (s istom shemom izmjena kao gore).
  • Skup polja neobrađeno je izvješće o završetku poziva i može sadržavati dodatna interna polja osim onih u tablici iznad — nepoznata polja tretirajte kao informativna.
  • Web-pozivi (direction: "web") izostavljaju from_number / to_number i dodaju origin_domain.
  • Pozivi za test mikrofona u Builderu prijavljuju se kao telephony.complete na naslijeđenoj putanji (sustav krajnje točke mapira ih na web.complete).
  • Koordinacija preusmjeravanja: kada poziv završi preusmjeravanjem, naslijeđeni webhook poziva se sinkrono i može odgovoriti {"transfer_ready": false} kako bi signalizirao da odredište preusmjeravanja nije spremno. Svaki drugi odgovor (ili izostanak naslijeđenog webhooka) omogućuje nastavak preusmjeravanja. Isporuke krajnjoj točki nikada se ne provjeravaju za to.

Primjer rukovatelja

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

Uobičajeni slučajevi upotrebe

Integracija s CRM-om

Pohranite transkript svakog poziva i URL snimke uz evidenciju svojih korisnika.

Analitika

Usmjerite transkripte u kanal za modeliranje tema, izdvajanje CSAT signala ili praćenje stope prijenosa.

Provjera kvalitete

Otvorite pozive u alatu za osiguranje kvalitete radi ljudske provjere ili ih obradite svojim modelom za evaluaciju.

Obavijesti

Aktivirajte ljudskog člana tima pri prijenosu / neuspjehu.


Povezano