ThunderPhone 2.0 jau čia.Viską atlikite savarankiškai – nuo 2 ct/min.Skaityti pranešimą

Webhooks

telephony.complete / web.complete

Neblokuojantis žiniatinklio kabliukas, pateikiamas pasibaigus skambučiui, su transkriptu, įrašo URL ir metrika.

Užbaigimo įvykis suaktyvinamas pasibaigus kiekvienam skambučiui – įeinančios telefonijos, išeinančios telefonijos, žiniatinklio skambučiui arba bandomajam skambučiui (kūrimo priemonės mikrofono seansui). Jis yra neblokuojantis: atsakykite bet kokiu 2xx kodu.

Įvykis pristatomas abiem keliais:

Užklausos paketas (galinių taškų pristatymai)

{
  "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"
}
LaukasTipasAprašymas
call_idintegerStabilus visuose šio skambučio įvykiuose
agent_idinteger | nullSkambutį aptarnavęs agentas, jei jis buvo priskirtas
agent_namestring | nullSkambutį aptarnavusio agento pavadinimas, jei jis buvo priskirtas
directionstringinbound, outbound, web, test. Istoriniuose paketuose gali būti senesnės mic arba widget reikšmės
from_number, to_numberstringE.164. Žiniatinklio ir bandomiesiems skambučiams from_number yra pažodinė reikšmė "web"
origin_domainstringTik žiniatinklio ir bandomiesiems skambučiams – puslapio, kuriame buvo valdiklis, kilmė (mikrofono seansams tuščia)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullApskaičiuojama pagal pradžios ir pabaigos laiką
statusstringcompleted arba failed
end_reasonstringŽr. toliau pateiktą lentelę
product, voicestringSkambučio metu taikyta agento konfigūracija
transfer_numberstring | nullNustatoma, kai skambutis buvo peradresuotas
recording_urlstring | nullGaliojanti pasirašyta URL; atsisiųskite nedelsdami. null, kai nėra įrašo artefakto
billable_minutesnumberApmokestinamos minutės, suapvalintos iki artimiausio ketvirčio minutės (15 sekundžių intervalai, mažiausiai 0,25). Tiesiai į balso paštą nukreipti skambučiai čia vis tiek nurodo faktines apskaitytas minutes, tačiau mokestis ribojamas iki vienos minutės pagal plano tarifą.
billing_total_centsintegerJAV dolerių centai
transcriptsarrayKiekvieno pokalbio posūkio transkripto įrašai; gali būti tuščias, kai transkriptas nepasiekiamas

Pabaigos priežastys

ReikšmėReikšmė
user_hangupKita šalis pirmoji padėjo ragelį
ai_hangupAI sąmoningai baigė skambutį
ai_transferAI peradresavo skambutį; nustatytas transfer_number
ai_warm_transferAI užbaigė šiltąjį peradresavimą su dalyvavimu
voicemail_hangupAptiktas balso paštas ir skambutis baigtas pagal jūsų voicemail_action
max_durationSkambutis pasiekė maksimalią trukmės ribą
supersededSeansą pakeitė naujesnis seansas
unknownNepavyko nustatyti pabaigos priežasties

Transkripto formatas

Kiekvienas transcripts įrašas yra vienas pokalbio ėjimas. Vaidmenys yra user (skambinančiojo kalba), model (agento kalba ir įrankių iškvietimai), tool (įrankių rezultatai) ir system (skambučio įvykiai, pvz., kalbos perjungimai).

[
  {
    "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"] }
    }
  }
]
LaukasTipasAprašymas
roleeilutėuser, model, tool arba system
content_typeeilutėtext/plain kalbai; application/json įrankių iškvietimams, įrankių rezultatams ir sistemos įvykiams
contenteilutė | objektasKalbos tekstas arba pirmiau parodytas struktūrizuotas objektas. Įrankių iškvietimai: {"tool_call": name, "arguments": {…}}. Įrankių rezultatai: {"tool_name": name, "response": {…}}
start_ms, end_mssveikasis skaičiusPoslinkiai nuo skambučio pradžios, ms. Pateikiami, kai žinomas garso laikas
ttfa_mssveikasis skaičiusLaikas iki pirmojo garso model ėjimui, kai išmatuotas
audio_url, audio_urlseilutė / masyvasBaigiančio galioti pasirašyti URL ėjimo garso įrašui, kai įrašoma kiekvienam ėjimui

Visai struktūrizuotai ėjimų istorijai (su pertraukimo žymenimis, patvirtinimo raginimais ir neapdorotomis pozicijomis) naudokite GET /v1/calls/{call_id}/history.

Ankstesnės versijos naudingosios apkrovos skirtumai

Ankstesnės versijos vieno URL žiniatinklio kablio apvalkalas yra {"type": "telephony.complete" | "web.complete", "data": {…}} be event_id, o jo data skiriasi nuo galinio taško naudingosios apkrovos:

Ankstesnės versijos užbaigimo naudingoji apkrova taip pat turi agent_id ir agent_name.

  • Ėjimų masyvas yra history, o ne transcripts (ta pati ėjimo schema kaip pirmiau).
  • Laukų rinkinys yra neapdorota skambučio pabaigos ataskaita ir gali apimti papildomus vidinius laukus, kurių nėra pirmiau pateiktoje lentelėje — nežinomus laukus laikykite informaciniais.
  • Žiniatinklio skambučiai (direction: "web") nepateikia from_number / to_number ir prideda origin_domain.
  • Konstruktoriaus mikrofono bandomieji skambučiai ankstesnės versijos kelyje pateikiami kaip telephony.complete (galinio taško sistema juos susieja su web.complete).
  • Perdavimo koordinavimas: kai skambutis baigiasi perdavimu, ankstesnės versijos žiniatinklio kablys iškviečiamas sinchroniškai ir gali atsakyti {"transfer_ready": false}, kad nurodytų, jog perdavimo paskirties vieta nėra parengta. Bet koks kitas atsakymas (arba ankstesnės versijos žiniatinklio kablio nebuvimas) leidžia tęsti perdavimą. Galinio taško pristatymai tam niekada nenaudojami.

Pavyzdinė apdorojimo funkcija

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

Dažniausi naudojimo atvejai

CRM integracija

Išsaugokite kiekvieno skambučio transkriptą ir įrašo URL kartu su klientų įrašais.

Analitika

Siųskite transkriptus į duomenų srautą temų modeliavimui, CSAT signalų išskyrimui arba perdavimo rodiklio stebėjimui.

Kokybės peržiūra

Atidarykite skambučius QA įrankyje, kad juos peržiūrėtų žmonės, arba apdorokite juos savo vertinimo modeliu.

Pranešimai

Perduodant skambutį arba įvykus klaidai praneškite komandos nariui.


Susiję