ThunderPhone 2.0 er lansert.Kom i gang selv – fra 2¢/min.Les mer om lanseringen

Webhooks

telephony.complete / web.complete

Ikke-blokkerende webhook levert når en samtale avsluttes, med transkripsjon, URL til opptak og målinger.

En fullføringshendelse utløses etter at hver samtale avsluttes — innkommende telefoni, utgående telefoni, nettsamtale eller testsamtale (mikrofonøkt i byggeren). Den er ikke-blokkerende: svar med en hvilken som helst 2xx-status.

Hendelsen leveres via begge banene:

Forespørselsnyttelast (endepunktleveringer)

{
  "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"
}
FeltTypeBeskrivelse
call_idheltallStabil på tvers av alle hendelser for denne samtalen
agent_idheltall | nullStemmeagenten som håndterte samtalen, når en var tilordnet
agent_namestreng | nullStemmeagenten som håndterte samtalen, når en var tilordnet
directionstrenginbound, outbound, web, test. Historiske nyttelaster kan inneholde eldre mic- eller widget-verdier
from_number, to_numberstrengE.164. from_number er den bokstavelige verdien "web" for nettsamtaler og testsamtaler
origin_domainstrengKun web/test — sideopprinnelsen som var vert for widgeten (tom for mic-økter)
start_time, end_timetidsstempelISO 8601 UTC
duration_secondsheltall | nullAvledet fra start/slutt
statusstrengcompleted eller failed
end_reasonstrengSe tabellen nedenfor
product, voicestrengStemmeagentkonfigurasjonen som var aktiv på samtaletidspunktet
variablesobjektInndatavariabler med øyeblikksbilde fra da samtalen startet
unresolved_variablesmatriseVariabelnavn som refereres av samtalekonfigurasjonen, men som ikke ble oppgitt ved samtalestart
transfer_numberstreng | nullAngis når samtalen ble overført
recording_urlstreng | nullUtløpende signert URL; last ned raskt. null når ingen opptaksartefakt er tilgjengelig
recording_stopped_attidsstempel | nullNår opptaket ble stoppet før samtalen var over; ingenting etter dette tidspunktet er med i opptaket. null når opptaket fortsatte til slutten av samtalen, eller samtalen ikke ble tatt opp
recording_stopped_reasonstreng | nullHvorfor opptaket stoppet tidlig. warm_transfer: det stopper rett før en varm overføring ringer destinasjonen, og forblir avslått selv om overføringen mislykkes og innringeren kommer tilbake til stemmeagenten. Behandle andre verdier som informasjon; flere kan bli lagt til. null når recording_stopped_at er null
billable_minutestallFakturerte minutter, avrundet til nærmeste kvart minutt (intervaller på 15 sekunder, minimum 0.25). Samtaler som går rett til talepost rapporterer fortsatt sine faktiske målte minutter her, men kostnaden er begrenset til ett minutt til planprisen.
billing_total_centsheltallUSD-cent
transcriptsmatriseTranskripsjonsoppføringer per tur; kan være tom når en transkripsjon ikke er tilgjengelig
summarystreng | nullSammendrag etter samtalen i klart språk (to til fire setninger med løpende tekst, maksimalt 2 000 tegn, skrevet på samtalens språk: stemmeagentens primærspråk med mindre samtalen overstyrte det) når stemmeagenten har Generer et samtalesammendrag aktivert under Data som skal samles inn i stemmeagentbyggeren (call_summary_enabled på Agent) og genereringen lykkes; ellers null, inkludert for samtaler som er for korte til å oppsummere, samtaler uten transkripsjon og stemmeagenter med opptak deaktivert. Sammendraget genereres i samme steg etter samtalen som uthenting, så innstillingen Vent på uthentede data før webhooken for fullført samtale sendes (extraction_blocks_completion) avgjør om denne hendelsen venter på det: I en ventende modus er sammendraget klart før denne hendelsen sendes (selve sammendraget får maksimalt 30 sekunder innenfor stegets budsjett på 75 sekunder); når hendelsen ikke venter (never, eller en overføring under except_transfers), er dette feltet null, og sammendraget sendes ikke på nytt i call.data_extracted, så les det senere fra summary på Call-objektet. Også tilgjengelig på samtalesiden i dashbordet
extracted_dataobjekt | nullStrukturert uthentingsresultat med status, fields, evidence, verification, field_reasons og schema_version. Hvert felt som ikke er null, har det nøyaktige strukturelt kontrollerte sitatet (maksimalt 1 000 tegn), samt talerrollen og turindeksen; lengre sitater returnert av modellen avvises i stedet for å avkortes. Bevis er null når feltet er null. verified betyr at hver kandidat mottok nøyaktig én gyldig, uavhengig vurdering. unavailable dekker også feilformatert eller delvis verifikatorutdata; gyldige delvise vurderinger gjelder fortsatt, mens kandidater uten én gyldig vurdering settes til null. status er completed, failed, exhausted, skipped eller skipped_recording_disabled; null når stemmeagenten ikke hadde noen uthentingsfelt
steps_tracematriseBare for samtaler der stemmeagenten har steg eller overleveringer. Rekkefølgen samtalen beveget seg gjennom dem i: Hver oppføring har t_ms (millisekunder siden samtalen startet), agent (stemmeagentens nøkkel, a pluss ID-en), step (steg-ID-en, eller null ved en overlevering til en stemmeagent uten steg) og via (start, model, condition, on_enter eller handoff). Inneholder bare ID-er og tidspunkter, så den beholdes selv når opptak er deaktivert
collected_valuesobjektBare for samtaler der stemmeagenten har steg eller overleveringer. Verdiene stemmeagenten samlet inn i stegene sine, nøkkelbasert på navnet på innsamlingsfeltet, samt <tool>_result for hvert stegs on_enter_tool-resultat. Utelates når samtalens innhold ikke beholdes (opptak deaktivert)

Sluttårsaker

VerdiBetydning
user_hangupDen eksterne parten la på først
ai_hangupAI-en avsluttet samtalen med hensikt
ai_transferAI-en videresendte samtalen; transfer_number er angitt
ai_warm_transferAI-en fullførte en varm (betjent) viderekobling
voicemail_hangupTelefonsvarer ble oppdaget, og samtalen ble avsluttet i henhold til voicemail_action
max_durationSamtalen nådde maksimal varighetsgrense: agentens eller samtalens egen grense, eller plattformgrensen på 6 timer. En samtale som AI-en eller innringeren avslutter etter advarselen om tidsgrensen, beholder ai_hangup eller user_hangup
supersededØkten ble erstattet av en nyere økt
inbound_config_webhook_failedInnkommende samtale til et nummer i webhook-modus: konfigurasjonswebhooken returnerte ikke en brukbar konfigurasjon, så samtalen ble avsluttet før en agent svarte. status er failed, og samtalen faktureres ikke
unknownAvslutningsårsaken kunne ikke fastslås

Transkriptformat

Hver oppføring i transcripts er én samtaleomgang. Rollene er user (innringerens tale), model (agentens tale og verktøykall), tool (verktøyresultater) og system (anropshendelser, som språkbytter).

[
  {
    "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"] }
    }
  }
]
FeltTypeBeskrivelse
rolestrenguser, model, tool eller system
content_typestrengtext/plain for tale; application/json for verktøykall, verktøyresultater og systemhendelser
contentstreng | objektTaletekst eller det strukturerte objektet vist ovenfor. Verktøykall: {"tool_call": name, "arguments": {…}}. Verktøyresultater: {"tool_name": name, "response": {…}}
start_ms, end_msheltallForskyvninger fra anropsstart, i ms. Finnes når lydtiming er kjent
ttfa_msheltallTid til første lyd for en model-omgang, når målt
audio_url, audio_urlsstreng / matriseUtløpende signerte URL-er for omgangens lyd, når lyd tas opp per omgang

For den fullstendig strukturerte omgangshistorikken (med avbruddsmarkører, bekreftelsesforespørsler og rå posisjoner), bruk GET /v1/calls/{call_id}/history.

Forskjeller i eldre nyttelast

Den eldre webhook-innpakningen med én URL er {"type": "telephony.complete" | "web.complete", "data": {…}} uten event_id, og data-feltet skiller seg fra endepunktets nyttelast:

Den eldre fullføringsnyttelasten inneholder også agent_id og agent_name.

  • Omgangslisten ligger under history, ikke transcripts (samme omgangsskjema som ovenfor).
  • Feltsettet er den rå rapporten ved anropsslutt og kan inneholde ekstra interne felt utover tabellen ovenfor — behandle ukjente felt som informasjon.
  • Nettanrop (direction: "web") utelater from_number / to_number og legger til origin_domain.
  • Byggerens mikrofontestanrop rapporteres som telephony.complete på den eldre banen (endepunktssystemet tilordner dem til web.complete).
  • Koordinering av overføring: Når et anrop avsluttes med en overføring, kalles den eldre webhooken synkront og kan svare {"transfer_ready": false} for å signalisere at målet for overføringen ikke er klart. Ethvert annet svar (eller ingen eldre webhook) lar overføringen fortsette. Leveringer til endepunktet blir aldri konsultert for dette.

Eksempelbehandler

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

Vanlige bruksområder

CRM-integrasjon

Lagre hvert samtaletranskript og hver opptaks-URL sammen med kundepostene dine.

Analyse

Send transkripter til en pipeline for emnemodellering, uttrekking av CSAT-signaler eller overvåking av overføringsrate.

Kvalitetsgjennomgang

Åpne samtaler i et QA-verktøy for menneskelig gjennomgang, eller kjør dem gjennom din egen evalueringsmodell.

Varsler

Varsle en menneskelig kollega ved overføring eller feil.


Relatert