ThunderPhone 2.0 ir klāt.Sāciet patstāvīgi — no 2 ¢/min.Lasīt paziņojumu

Webhooks

telephony.complete / web.complete

Nebloķējošs tīmekļa aizķeres paziņojums, kas tiek nosūtīts, kad zvans beidzas, ar transkriptu, ieraksta URL un rādītājiem.

Pabeigšanas notikums tiek aktivizēts pēc katra zvana beigām — ienākošās telefonijas, izejošās telefonijas, tīmekļa zvana vai testa zvana (veidotāja mikrofona sesijas). Tas ir nebloķējošs: atbildiet ar jebkuru 2xx.

Notikums tiek nosūtīts pa abiem ceļiem:

Pieprasījuma slodze (galapunkta piegādes)

{
  "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"
}
LauksTipsApraksts
call_idintegerNemainīgs visos šī zvana notikumos
agent_idinteger | nullBalss aģents, kas apstrādāja zvanu, ja tāds bija piešķirts
agent_namestring | nullBalss aģents, kas apstrādāja zvanu, ja tāds bija piešķirts
directionstringinbound, outbound, web, test. Vēsturiskajās slodzēs var būt mantotās vērtības mic vai widget
from_number, to_numberstringE.164. Tīmekļa un testa zvaniem from_number burtiski ir "web"
origin_domainstringTikai tīmeklim/testam — lapas izcelsme, kurā tika mitināts logrīks (tukša mikrofona sesijām)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullAtvasināts no sākuma/beigu laika
statusstringcompleted vai failed
end_reasonstringSkatiet tālāk esošo tabulu
product, voicestringZvana laikā spēkā esošā balss aģenta konfigurācija
variablesobjectIevades mainīgie, kuru momentuzņēmums tika izveidots zvana sākumā
unresolved_variablesarrayMainīgo nosaukumi, uz kuriem atsaucas zvana konfigurācija, bet kuri netika norādīti zvana sākumā
transfer_numberstring | nullIestatīts, kad zvans tika pārsūtīts
recording_urlstring | nullParakstīts URL ar derīguma termiņu; lejupielādējiet nekavējoties. null, ja ieraksta artefakts nav pieejams
billable_minutesnumberRēķinā iekļautās minūtes, noapaļotas līdz tuvākajai ceturtdaļminūtei (15 sekunžu intervāli, minimums 0,25). Zvani, kas uzreiz nonāk balss pastā, arī šeit norāda faktiskās uzskaitītās minūtes, taču maksa ir ierobežota līdz vienai minūtei pēc plāna tarifa.
billing_total_centsintegerASV dolāru centi
transcriptsarrayTranskripcijas ieraksti katram gājienam; var būt tukšs, ja transkripcija nav pieejama
extracted_dataobject | nullStrukturēts ekstrakcijas rezultāts ar status, fields, evidence, verification, field_reasons un schema_version. Katram laukam, kas nav null, ir precīzs strukturāli pārbaudīts citāts (ne vairāk kā 1000 rakstzīmes), kā arī runātāja loma un gājiena indekss; garāki modeļa atgrieztie citāti tiek noraidīti, nevis saīsināti. Pierādījums ir null, ja lauks ir null. verified nozīmē, ka katrs kandidāts saņēma tieši vienu derīgu neatkarīgu vērtējumu. unavailable aptver arī nepareizu vai daļēju verificētāja izvadi; derīgi daļēji vērtējumi joprojām tiek piemēroti, bet kandidāti bez viena derīga vērtējuma tiek iestatīti uz nulli. status ir completed, failed, exhausted, skipped vai skipped_recording_disabled; null, ja balss aģentam nebija ekstrakcijas lauku

Beigu iemesli

VērtībaNozīme
user_hangupAttālā puse pirmā nolika klausuli
ai_hangupMI apzināti beidza zvanu
ai_transferMI pārsūtīja zvanu; ir iestatīts transfer_number
ai_warm_transferMI pabeidza silto (ar apstiprinājumu) pārsūtīšanu
voicemail_hangupTika noteikts balss pasts, un zvans tika pabeigts atbilstoši jūsu voicemail_action
max_durationZvans sasniedza maksimālā ilguma ierobežojumu
supersededSesija tika aizstāta ar jaunāku sesiju
unknownBeigu iemeslu nevarēja noteikt

Transkripta formāts

Katrs ieraksts transcripts masīvā ir viena sarunas kārta. Lomas ir user (zvanītāja runa), model (balss aģenta runa un rīku izsaukumi), tool (rīku rezultāti) un system (zvana notikumi, piemēram, valodas maiņa).

[
  {
    "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"] }
    }
  }
]
LauksTipsApraksts
rolevirkneuser, model, tool vai system
content_typevirknetext/plain runai; application/json rīku izsaukumiem, rīku rezultātiem un sistēmas notikumiem
contentvirkne | objektsRunas teksts vai iepriekš parādītais strukturētais objekts. Rīku izsaukumi: {"tool_call": name, "arguments": {…}}. Rīku rezultāti: {"tool_name": name, "response": {…}}
start_ms, end_msvesels skaitlisNobīdes no zvana sākuma milisekundēs. Tiek iekļautas, ja ir zināms audio laiks
ttfa_msvesels skaitlisLaiks līdz pirmajam audio model kārtai, ja tas ir izmērīts
audio_url, audio_urlsvirkne / masīvsParakstīti URL ar derīguma termiņu kārtas audio ierakstam, ja audio tiek ierakstīts katrai kārtai

Pilnībā strukturētai kārtu vēsturei (ar pārtraukumu marķieriem, apstiprinājuma uzvednēm un neapstrādātām pozīcijām) izmantojiet GET /v1/calls/{call_id}/history.

Mantotās slodzes atšķirības

Mantotās viena URL tīmekļa āķa aploksne ir {"type": "telephony.complete" | "web.complete", "data": {…}} bez event_id, un tās data atšķiras no galapunkta slodzes:

Mantotā pabeigšanas slodze ietver arī agent_id un agent_name.

  • Kārtu masīvs atrodas sadaļā history, nevis transcripts (tāda pati kārtu shēma kā iepriekš).
  • Lauku kopa ir neapstrādāts zvana beigu pārskats, un tajā var būt papildu iekšējie lauki, kas nav norādīti iepriekšējā tabulā — nezināmus laukus uzskatiet par informatīviem.
  • Tīmekļa zvani (direction: "web") neiekļauj from_number / to_number un pievieno origin_domain.
  • Veidotāja mikrofona pārbaudes zvani mantotajā ceļā tiek ziņoti kā telephony.complete (galapunkta sistēma tos kartē uz web.complete).
  • Pārsūtīšanas koordinēšana: ja zvans beidzas ar pārsūtīšanu, mantotais tīmekļa āķis tiek izsaukts sinhroni un var atbildēt ar {"transfer_ready": false}, lai norādītu, ka pārsūtīšanas mērķis nav gatavs. Jebkura cita atbilde (vai mantotā tīmekļa āķa neesamība) ļauj pārsūtīšanai turpināties. Galapunktu piegādes šim nolūkam nekad netiek izmantotas.

Piemēra apstrādātājs

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

Biežākie lietošanas gadījumi

CRM integrācija

Saglabājiet katra zvana transkriptu un ieraksta URL kopā ar klientu ierakstiem.

Analītika

Straumējiet transkriptus uz konveijeru tēmu modelēšanai, CSAT signālu iegūšanai vai pārsūtīšanas rādītāja uzraudzībai.

Kvalitātes pārbaude

Atveriet zvanus kvalitātes nodrošināšanas rīkā cilvēka veiktai pārbaudei vai palaidiet tos savā novērtēšanas modelī.

Paziņojumi

Pārsūtīšanas vai kļūmes gadījumā aktivizējiet cilvēku no komandas.


Saistītā informācija