ThunderPhone 2.0 ist live.Direkt im Self-Service – ab 2 ¢/Min..Ankündigung lesen

Webhooks

telephony.complete / web.complete

Nicht blockierender Webhook, der beim Ende eines Anrufs mit Transkript, Aufzeichnungs-URL und Metriken bereitgestellt wird.

Ein Abschlussereignis wird ausgelöst, nachdem jeder Anruf endet — eingehende Telefonie, ausgehende Telefonie, Webanruf oder Testanruf (Builder-Mikrofonsitzung). Es ist nicht blockierend: Antworten Sie mit einem beliebigen 2xx-Status.

Das Ereignis wird über beide Pfade zugestellt:

Anforderungsnutzlast (Endpunkt-Zustellungen)

{
  "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"
}
FeldTypBeschreibung
call_idintegerÜber alle Ereignisse für diesen Anruf hinweg stabil
agent_idinteger | nullAgent, der den Anruf bearbeitet hat, sofern einer zugewiesen war
agent_namestring | nullAgent, der den Anruf bearbeitet hat, sofern einer zugewiesen war
directionstringinbound, outbound, web, test. Historische Payloads können die veralteten Werte mic oder widget enthalten
from_number, to_numberstringE.164. from_number ist bei Webanrufen und Testanrufen wörtlich "web"
origin_domainstringNur Web/Test — der Seitenursprung, der das Widget gehostet hat (bei Mic-Sitzungen leer)
start_time, end_timetimestampISO 8601 UTC
duration_secondsinteger | nullAus Start- und Endzeit abgeleitet
statusstringcompleted oder failed
end_reasonstringSiehe Tabelle unten
product, voicestringZum Zeitpunkt des Anrufs wirksame Agentenkonfiguration
variablesobjectBeim Start des Anrufs erfasste Eingabevariablen
unresolved_variablesarrayVariablennamen, auf die die Anrufkonfiguration verweist, die aber beim Anrufstart nicht bereitgestellt wurden
transfer_numberstring | nullWird gesetzt, wenn der Anruf weitergeleitet wurde
recording_urlstring | nullAblaufende signierte URL; laden Sie sie zeitnah herunter. null, wenn kein Aufzeichnungsartefakt verfügbar ist
recording_stopped_attimestamp | nullZeitpunkt, zu dem die Aufzeichnung vor dem Ende des Anrufs beendet wurde; nach diesem Zeitpunkt ist nichts mehr in der Aufzeichnung enthalten. null, wenn die Aufzeichnung bis zum Ende des Anrufs lief oder der Anruf nicht aufgezeichnet wurde
recording_stopped_reasonstring | nullGrund, warum die Aufzeichnung vorzeitig beendet wurde. warm_transfer: Sie wird unmittelbar vor dem Anruf einer Warmübergabe an das Ziel beendet und bleibt deaktiviert, auch wenn die Weiterleitung fehlschlägt und der Anrufer zum Agenten zurückkehrt. Behandeln Sie andere Werte als Informationen; weitere können hinzugefügt werden. null, wenn recording_stopped_at null ist
billable_minutesnumberAbgerechnete Minuten, auf die nächste Viertelminute gerundet (15-Sekunden-Schritte, mindestens 0,25). Anrufe, die direkt auf der Mailbox landen, geben hier weiterhin ihre tatsächlich gemessenen Minuten an, die Gebühr ist jedoch auf eine Minute zum Plantarif begrenzt.
billing_total_centsintegerUS-Cent
transcriptsarrayTranskripteinträge pro Gesprächszug; kann leer sein, wenn kein Transkript verfügbar ist
summarystring | nullVerständliche Zusammenfassung nach dem Anruf (zwei bis vier Prosasätze, höchstens 2.000 Zeichen, in der Sprache des Anrufs: der primären Sprache des Agenten, sofern der Anruf sie nicht überschrieben hat), wenn der Agent im Agent Builder unter Zu erhebende Daten Anrufzusammenfassung generieren aktiviert hat (call_summary_enabled beim Agenten) und die Generierung erfolgreich war; andernfalls null, auch bei Anrufen, die zu kurz für eine Zusammenfassung sind, Anrufen ohne Transkript und Agenten mit deaktivierter Aufzeichnung. Die Zusammenfassung wird im selben Schritt nach dem Anruf wie die Extraktion generiert. Daher entscheidet die Einstellung Vor dem Senden des Webhooks „Anruf beendet“ auf extrahierte Daten warten (extraction_blocks_completion), ob dieses Ereignis darauf wartet: In einem Wartemodus ist die Zusammenfassung fertig, bevor dieses Ereignis gesendet wird (die Zusammenfassung selbst erhält innerhalb des 75-Sekunden-Budgets des Schritts höchstens 30 Sekunden); wenn das Ereignis nicht wartet (never oder eine Weiterleitung unter except_transfers), ist dieses Feld null und die Zusammenfassung wird nicht erneut in call.data_extracted gesendet. Lesen Sie sie daher später aus summary im Anrufobjekt. Auch auf der Anrufseite im Dashboard verfügbar
extracted_dataobject | nullStrukturiertes Extraktionsergebnis mit status, fields, evidence, verification, field_reasons und schema_version. Jedes Feld mit einem Wert ungleich null enthält das exakt strukturell geprüfte Zitat (höchstens 1.000 Zeichen) sowie Sprecherrolle und Gesprächszugindex; längere vom Modell zurückgegebene Zitate werden abgelehnt statt gekürzt. Evidenz ist null, wenn das Feld null ist. verified bedeutet, dass jeder Kandidat genau ein gültiges unabhängiges Urteil erhalten hat. unavailable umfasst auch fehlerhafte oder unvollständige Verifier-Ausgaben; gültige Teilurteile werden weiterhin angewendet, während Kandidaten ohne ein gültiges Urteil auf null gesetzt werden. status ist completed, failed, exhausted, skipped oder skipped_recording_disabled; null, wenn der Agent keine Extraktionsfelder hatte
steps_tracearrayNur bei Anrufen, deren Agent Schritte oder Übergaben hat. Die Reihenfolge, in der der Anruf sie durchlaufen hat: Jeder Eintrag enthält t_ms (Millisekunden seit Anrufbeginn), agent (den Schlüssel des Agenten, a plus seine ID), step (die Schritt-ID oder null bei einer Übergabe an einen Agenten ohne Schritte) und via (start, model, condition, on_enter oder handoff). Enthält nur IDs und Zeiten und wird daher auch bei deaktivierter Aufzeichnung beibehalten
collected_valuesobjectNur bei Anrufen, deren Agent Schritte oder Übergaben hat. Die Werte, die der Agent in seinen Schritten erfasst hat, nach dem Namen des Erfassungsfelds gruppiert, sowie <tool>_result für das Ergebnis von on_enter_tool jedes Schritts. Wird ausgelassen, wenn der Inhalt des Anrufs nicht gespeichert wird (Aufzeichnung deaktiviert)

Endgründe

WertBedeutung
user_hangupDie Gegenpartei hat zuerst aufgelegt
ai_hangupDie KI hat den Anruf absichtlich beendet
ai_transferDie KI hat den Anruf weitergeleitet; transfer_number ist gesetzt
ai_warm_transferDie KI hat eine betreute Weiterleitung abgeschlossen
voicemail_hangupEine Mailbox wurde erkannt und der Anruf gemäß Ihrer voicemail_action beendet
max_durationDer Anruf hat die maximale Dauer erreicht: das Limit des Agenten, das eigene Limit des Anrufs oder das Plattformlimit von 6 Stunden. Ein Anruf, den die KI oder der Anrufer nach der Warnung zum Zeitlimit beendet, behält ai_hangup oder user_hangup bei
supersededDie Sitzung wurde durch eine neuere ersetzt
inbound_config_webhook_failedEingehender Anruf an eine Nummer im Webhook-Modus: Der Konfigurations-Webhook hat keine verwendbare Konfiguration zurückgegeben, daher wurde der Anruf beendet, bevor ein Agent antwortete. status ist failed und der Anruf wird nicht abgerechnet
unknownDer Grund für die Beendigung konnte nicht ermittelt werden

Transkriptformat

Jeder Eintrag in transcripts ist ein Gesprächszug. Rollen sind user (Sprache des Anrufers), model (Sprache des Sprachagenten und Tool-Aufrufe), tool (Tool-Ergebnisse) und system (Anrufereignisse wie Sprachwechsel).

[
  {
    "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"] }
    }
  }
]
FeldTypBeschreibung
roleStringuser, model, tool oder system
content_typeStringtext/plain für Sprache; application/json für Tool-Aufrufe, Tool-Ergebnisse und Systemereignisse
contentString | ObjektSprachtext oder das oben gezeigte strukturierte Objekt. Tool-Aufrufe: {"tool_call": name, "arguments": {…}}. Tool-Ergebnisse: {"tool_name": name, "response": {…}}
start_ms, end_msGanzzahlVersätze ab Anrufbeginn in ms. Vorhanden, wenn Audio-Timing bekannt ist
ttfa_msGanzzahlZeit bis zum ersten Audio eines model-Zugs, sofern gemessen
audio_url, audio_urlsString / ArrayAblaufende signierte URLs für das Audio des Zugs, wenn pro Zug aufgezeichnet

Verwenden Sie für den vollständig strukturierten Zugverlauf (mit Unterbrechungsmarkierungen, Bestätigungsaufforderungen und Rohpositionen) GET /v1/calls/{call_id}/history.

Unterschiede bei Legacy-Payloads

Das Legacy-Webhook-Format mit einer einzelnen URL lautet {"type": "telephony.complete" | "web.complete", "data": {…}} und enthält keine event_id; sein data unterscheidet sich von der Endpoint-Payload:

Die Legacy-Abschluss-Payload enthält außerdem agent_id und agent_name.

  • Das Zug-Array befindet sich unter history, nicht unter transcripts (gleiches Zug-Schema wie oben).
  • Die Feldmenge ist der rohe Bericht zum Anrufende und kann zusätzliche interne Felder über die obige Tabelle hinaus enthalten — behandeln Sie unbekannte Felder als Informationen.
  • Web-Anrufe (direction: "web") lassen from_number / to_number weg und fügen origin_domain hinzu.
  • Builder-Mikrofontest-Anrufe werden im Legacy-Pfad als telephony.complete gemeldet (das Endpoint-System ordnet sie web.complete zu).
  • Transferkoordination: Wenn ein Anruf während eines Transfers endet, wird der Legacy-Webhook synchron aufgerufen und kann {"transfer_ready": false} zurückgeben, um zu signalisieren, dass das Übergabeziel nicht bereit ist. Jede andere Antwort (oder kein Legacy-Webhook) ermöglicht die Fortsetzung des Transfers. Endpoint-Zustellungen werden hierfür niemals berücksichtigt.

Beispiel-Handler

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

Häufige Anwendungsfälle

CRM-Integration

Speichern Sie das Transkript und die Aufzeichnungs-URL jedes Anrufs zusammen mit Ihren Kundendaten.

Analysen

Streamen Sie Transkripte an eine Pipeline für Themenmodellierung, die Extraktion von CSAT-Signalen oder die Überwachung der Weiterleitungsrate.

Qualitätsprüfung

Öffnen Sie Anrufe zur manuellen Prüfung in einem QA-Tool oder lassen Sie sie durch Ihr eigenes Bewertungsmodell laufen.

Benachrichtigungen

Benachrichtigen Sie bei Weiterleitung oder Fehler einen menschlichen Teamkollegen.


Verwandte Inhalte