ThunderPhone 2.0 är här.Kom igång på egen hand, från 2 ¢/min.Läs lanseringsnyheten

Webhooks

telephony.complete / web.complete

Icke-blockerande webhook som skickas när ett samtal avslutas, med transkription, inspelnings-URL och mätvärden.

En slutförandehändelse utlöses efter att varje samtal avslutas — inkommande telefoni, utgående telefoni, webbsamtal eller testsamtal (byggarens mikrofonsession). Den är icke-blockerande: svara med valfri 2xx.

Händelsen levereras via båda vägarna:

Begärandenyttolast (endpointleveranser)

{
  "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_stopped_at": null,
    "recording_stopped_reason": null,
    "recording_url": "https://storage.example.com/…",
    "start_time": "2026-04-20T18:24:10.113Z",
    "status": "completed",
    "summary": "Alex Morgan called to book a follow-up visit and confirmed Thursday, April 23. The agent read the appointment back and Alex asked for a reminder text the day before.",
    "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"
}
FältTypBeskrivning
call_idheltalStabilt för varje händelse för detta samtal
agent_idheltal | nullAgent som hanterade samtalet, när en tilldelades
agent_namesträng | nullAgent som hanterade samtalet, när en tilldelades
directionstränginbound, outbound, web, test. Historiska nyttolaster kan innehålla äldre värden för mic eller widget
from_number, to_numbersträngE.164. from_number är den bokstavliga strängen "web" för webbsamtal och testsamtal
origin_domainsträngEndast webb/test — sidans ursprung som var värd för widgeten (tomt för mikrofonsessioner)
start_time, end_timetidsstämpelISO 8601 UTC
duration_secondsheltal | nullHärleds från start/slut
statussträngcompleted eller failed
end_reasonsträngSe tabellen nedan
product, voicesträngAgentkonfiguration som gällde vid samtalstillfället
variablesobjektIndatavariabler som sparades som ögonblicksbild när samtalet startade
unresolved_variablesmatrisVariabelnamn som refereras av samtalskonfigurationen men inte angavs när samtalet startade
transfer_numbersträng | nullAnges när samtalet överfördes
recording_urlsträng | nullTidsbegränsad signerad URL; ladda ned den omgående. null när ingen inspelningsartefakt är tillgänglig
recording_stopped_attidsstämpel | nullNär inspelningen stoppades innan samtalet avslutades; inget efter denna tidpunkt finns med i inspelningen. null när inspelningen fortsatte till slutet av samtalet eller samtalet inte spelades in
recording_stopped_reasonsträng | nullVarför inspelningen stoppades i förtid. warm_transfer: den stoppas precis innan en varm överföring ringer destinationen och förblir avstängd även om överföringen misslyckas och uppringaren återvänder till agenten. Behandla andra värden som informativa; fler kan läggas till. null när recording_stopped_at är null
billable_minutestalDebiterade minuter, avrundade till närmaste kvarts minut (intervall på 15 sekunder, minst 0.25). Samtal som går direkt till röstbrevlådan rapporterar fortfarande sina faktiska uppmätta minuter här, men avgiften är begränsad till en minut enligt planens pris.
billing_total_centsheltalAmerikanska cent
transcriptsmatrisTranskriptposter per tur; kan vara tom när ett transkript inte är tillgängligt
summarysträng | nullSammanfattning efter samtalet på klarspråk (två till fyra meningar löpande text, högst 2 000 tecken, skriven på samtalets språk: agentens primära språk om inte samtalet åsidosatte det) när agenten har Generera en samtalssammanfattning aktiverat under Data att samla in i agentbyggaren (call_summary_enabled på Agent) och genereringen lyckades; annars null, inklusive för samtal som är för korta för att sammanfatta, samtal utan transkript och agenter med inspelning avstängd. Sammanfattningen genereras i samma steg efter samtalet som extraheringen, så inställningen Vänta på extraherade data innan webhooken för avslutat samtal skickas (extraction_blocks_completion) avgör om denna händelse väntar på den: i ett vänteläge är sammanfattningen klar innan denna händelse skickas (sammanfattningen får högst 30 sekunder inom stegets budget på 75 sekunder); när händelsen inte väntar (never eller en överföring under except_transfers) är detta fält null och sammanfattningen skickas inte på nytt i call.data_extracted, så läs den senare från summary i Call-objektet. Finns även på samtalssidan i dashboarden
extracted_dataobjekt | nullStrukturerat extraheringsresultat med status, fields, evidence, verification, field_reasons och schema_version. Varje fält som inte är null har det exakta strukturellt kontrollerade citatet (högst 1 000 tecken), samt talarroll och turindex; längre citat som returneras av modellen avvisas i stället för att trunkeras. Evidens är null när fältet är null. verified innebär att varje kandidat fick exakt ett giltigt oberoende utlåtande. unavailable omfattar även felaktiga eller partiella verifierarutdata; giltiga partiella utlåtanden tillämpas fortfarande, medan kandidater utan ett giltigt utlåtande sätts till null. status är completed, failed, exhausted, skipped eller skipped_recording_disabled; null när agenten inte hade några extraheringsfält
steps_tracematrisEndast för samtal vars agent har steg eller överlämningar. Ordningen som samtalet rörde sig genom dem: varje post har t_ms (millisekunder sedan samtalet startade), agent (agentens nyckel, a plus dess ID), step (steg-ID:t, eller null vid en överlämning till en agent utan steg) och via (start, model, condition, on_enter eller handoff). Innehåller endast ID:n och tider, så den behålls även när inspelning är avstängd
collected_valuesobjektEndast för samtal vars agent har steg eller överlämningar. Värdena som agenten samlade in i sina steg, nycklade efter insamlingsfältnamn, plus <tool>_result för varje stegs on_enter_tool-resultat. Utelämnas när samtalets innehåll inte behålls (inspelning avstängd)

Avslutsorsaker

VärdeBetydelse
user_hangupMotparten lade på först
ai_hangupAI:n avslutade samtalet avsiktligt
ai_transferAI:n kopplade samtalet vidare; transfer_number är angivet
ai_warm_transferAI:n slutförde en varm (övervakad) vidarekoppling
voicemail_hangupRöstbrevlåda upptäcktes och samtalet avslutades enligt din voicemail_action
max_durationSamtalet nådde gränsen för maximal längd: agentens eller samtalets egen gräns, eller plattformens gräns på 6 timmar. Ett samtal som AI:n eller uppringaren avslutar efter varningen om tidsgränsen behåller ai_hangup eller user_hangup
supersededSessionen ersattes av en nyare
inbound_config_webhook_failedInkommande samtal till ett nummer i webhook-läge: konfigurationswebhooken returnerade inte en användbar konfiguration, så samtalet avslutades innan en agent svarade. status är failed och samtalet debiteras inte
unknownOrsaken till avslutet kunde inte fastställas

Transkriptformat

Varje post i transcripts är en samtalstur. Roller är user (uppringarens tal), model (agentens tal och verktygsanrop), tool (verktygsresultat) och system (samtalshändelser som språkbyten).

[
  {
    "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"] }
    }
  }
]
FältTypBeskrivning
rolestränguser, model, tool eller system
content_typesträngtext/plain för tal; application/json för verktygsanrop, verktygsresultat och systemhändelser
contentsträng | objektTaltext eller det strukturerade objektet som visas ovan. Verktygsanrop: {"tool_call": name, "arguments": {…}}. Verktygsresultat: {"tool_name": name, "response": {…}}
start_ms, end_msheltalFörskjutningar från samtalsstart, i ms. Finns när ljudtidtagning är känd
ttfa_msheltalTid till första ljudet för en model-tur, när den mäts
audio_url, audio_urlssträng / matrisTidsbegränsade signerade URL:er för turens ljud, när ljud spelas in per tur

Använd GET /v1/calls/{call_id}/history för den fullständigt strukturerade turhistoriken (med avbrottsmarkörer, bekräftelseuppmaningar och råa positioner).

Skillnader i äldre payload

Det äldre webhook-omslaget med en enda URL är {"type": "telephony.complete" | "web.complete", "data": {…}} utan något event_id, och dess data skiljer sig från endpoint-payloaden:

Den äldre slutförandepayloaden innehåller även agent_id och agent_name.

  • Turmatrisen finns under history, inte transcripts (samma turschema som ovan).
  • Fältuppsättningen är den råa rapporten vid samtalets slut och kan innehålla ytterligare interna fält utöver tabellen ovan — behandla okända fält som information.
  • Webbsamtal (direction: "web") utelämnar from_number / to_number och lägger till origin_domain.
  • Mikrofonsamtal för test i Builder rapporteras som telephony.complete på den äldre sökvägen (endpoint-systemet mappar dem till web.complete).
  • Överföringssamordning: när ett samtal avslutas med en överföring anropas den äldre webhooken synkront och kan svara {"transfer_ready": false} för att signalera att överföringens mål inte är redo. Alla andra svar (eller ingen äldre webhook) låter överföringen fortsätta. Endpoint-leveranser konsulteras aldrig för detta.

Exempelhanterare

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

Vanliga användningsfall

CRM-integration

Spara varje samtals transkript och inspelnings-URL tillsammans med dina kundregister.

Analys

Strömma transkript till en pipeline för ämnesmodellering, extrahering av CSAT-signaler eller övervakning av överföringsfrekvens.

Kvalitetsgranskning

Öppna samtal i ett QA-verktyg för manuell granskning, eller kör dem genom din egen utvärderingsmodell.

Aviseringar

Avisera en mänsklig teammedlem vid överföring eller fel.


Relaterat