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

Webhooks

telephony.incoming / web.incoming

Blockierender Webhook, der die Konfiguration eines eingehenden Anrufs in Echtzeit festlegt.

Wenn ein eingehender Anruf eine Nummer ohne zugewiesenen Agenten erreicht oder eine Web-Widget-Sitzung mit einem veröffentlichbaren Schlüssel im mode="webhook" startet, sendet ThunderPhone eine blockierende telephony.incoming / web.incoming-Anfrage an Ihre Legacy-Webhook-URL und wartet auf eine Konfigurationsantwort: bis zu 13 Sekunden für einen Anruf, einschließlich eines Wiederholungsversuchs, und bis zu 10 Sekunden für eine Widget-Sitzung (siehe Zeitüberschreitungen und Wiederholungsversuche). Verwenden Sie diesen Austausch, um Prompt, Stimme und Tools für jeden Anruf dynamisch auszuwählen — das vollständige Muster finden Sie im Leitfaden zur dynamischen Anrufkonfiguration.

Zeitüberschreitungen und Wiederholungsversuche

Antworten Sie schnell: Der Anrufer wartet in der Leitung, während Sie entscheiden.

Für einen Telefonanruf (telephony.incoming):

  • Der erste Versuch kann bis zu 10 Sekunden dauern.
  • Wenn er eine Zeitüberschreitung hat, keine Verbindung herstellen kann oder eine Antwort mit 5xx, 408 oder 429 erhält, versucht ThunderPhone es noch einmal. Für den Wiederholungsversuch steht der Rest eines 13-Sekunden-Budgets für den gesamten Austausch zur Verfügung: etwa 3 Sekunden nach einem ersten Versuch mit Zeitüberschreitung, nahezu alle 13 nach einem schnellen Fehler. Es erfolgt kein Wiederholungsversuch, wenn weniger als 2 Sekunden übrig wären.
  • Jede andere 4xx-Antwort, eine Antwort über dem Größenlimit oder eine 2xx-Antwort, deren Body keine gültige Konfiguration enthält, ist endgültig und wird nicht wiederholt.
  • Beide Versuche senden denselben Body, denselben Header X-ThunderPhone-Signature und denselben Header X-ThunderPhone-Idempotency-Key (telephony-incoming-<call_id>), sodass ein Handler mit Seiteneffekten den Wiederholungsversuch erkennen kann.

Es gibt keinen Fallback-Agenten. Wenn keine nutzbare Konfiguration eintrifft, wird der Anruf sofort beendet, statt den Anrufer in einer stillen Leitung warten zu lassen. Er erscheint im Anrufverlauf als fehlgeschlagener, nicht abgerechneter Anruf mit end_reason inbound_config_webhook_failed, und das übliche telephony.complete-Ereignis wird gesendet.

Eine Widget-Sitzung (web.incoming) erhält einen einzigen Versuch von bis zu 10 Sekunden. Wenn dieser fehlschlägt, schlägt die Sitzungsanfrage mit 502 oder 422 fehl.

Anfrage-Payload

Für Telefonanrufe (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
FeldTypBeschreibung
call_idintegerAnruf-ID — über alle Ereignisse für diesen Anruf hinweg stabil
from_numberstringE.164-Nummer des Anrufers
to_numberstringE.164-Zielnummer (eine Ihrer ThunderPhone-Nummern)

Bei Web-Widget-Sitzungen (web.incoming) identifiziert data statt Telefonnummern die einbettende Seite:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
FeldTypBeschreibung
call_idGanzzahlAnruf-ID
origin_domainZeichenfolgeDer Ursprung der Seite, die das Widget hostet, wie vom Browser übermittelt. Ein Nicht-Browser-Client kann einen beliebigen Wert senden. Behandeln Sie ihn daher nicht als Nachweis dafür, woher die Anfrage stammt
publishable_key_prefixZeichenfolgeErste Zeichen des veröffentlichbaren Schlüssels, der die Sitzung geöffnet hat
language, primary_languageZeichenfolgeVorhanden, wenn die Widget-Sitzung eine Sprachüberschreibung angefordert hat
voiceZeichenfolgeVorhanden, wenn die Widget-Sitzung eine Stimmenüberschreibung angefordert hat
website_contextZeichenfolgeVorhanden, wenn das Widget seitenbezogenen Kontext pro Sitzung übergeben hat

Die Überschreibungsfelder (language, voice, website_context und alle Browser-variables) werden nur übermittelt, wenn der veröffentlichbare Schlüssel sie in seinen Browser-Überschreibungen zulässt. Eine Sitzungsanfrage, die eine Überschreibung enthält, die der Schlüssel nicht zulässt, wird abgelehnt, bevor dieser Webhook aufgerufen wird.


Antwortschema

Geben Sie ein JSON-Objekt zurück, das die Agentenkonfiguration für diesen Anruf beschreibt. prompt und voice sind erforderlich; alles andere ist optional.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
FeldTypErforderlichBeschreibung
promptZeichenfolgejaSystem-Prompt, der den Agenten steuert
voiceZeichenfolgejaSprach-ID von GET /v1/voices, z. B. john. voice_name wird als Alias akzeptiert. Unbekannte Stimmen bestehen die Validierung nicht und lehnen den Anruf ab
productZeichenfolgeneinStandardmäßig spark. Zulässig: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelZeichenfolgeneinminimal, base (Standard) oder extra. Für Storm-Produkte überschrieben: storm-extra* erzwingt extra, andere storm-* erzwingen base
audio_context_modeZeichenfolgeneinfull (Standard) oder reduced
watchdog_enabledbooleschneinÜberwachung für diesen Anruf aktivieren. Standardmäßig false
additional_audio_contextboolesch | nullneinDie letzten paar Gesprächszüge des Anruferaudios statt nur des neuesten Gesprächszugs einbeziehen, um Korrekturen und die Erfassung von Daten mit vielen Buchstaben oder Zahlen bei geringfügig höherer Latenz und Kosten zu verbessern. Für eingehende Sitzungen standardmäßig aktiviert und für ausgehende Telefonanrufe deaktiviert; null behält den Standard bei
storm_feedback_modeZeichenfolgeneinnone, acknowledgement (Standard) oder tick
languageZeichenfolgeneinKurzform für primary_language
primary_languageZeichenfolgeneinSprachcode, normalisiert (Standard en). Nicht auflösbare Codes lehnen den Anruf ab
has_additional_languagesbooleschneinStandardmäßig false
additional_languagesArray von ZeichenfolgenneinZusätzliche Sprachen, zu denen der Agent wechseln kann
native_voice_switchingbooleschneinStandardmäßig false. Wenn der Anruf zu einer anderen Sprache wechselt, wird zu einer in dieser Sprache heimischen Stimme gewechselt, die nach Geschlecht übereinstimmt, statt die konfigurierte Stimme beizubehalten
language_switchingZeichenfolge | nullneinask_first (Standard) oder follow_caller; null entspricht ask_first. Legt fest, wie ein mehrsprachiger Agent damit umgeht, wenn die andere Person eine andere seiner Sprachen spricht. ask_first wechselt sofort, wenn die Person nach der Sprache fragt oder einen vollständigen Satz darin sagt, und fragt nach etwas Kurzem wie einem „ja“, ob darin fortgefahren werden soll. follow_caller wechselt zu jeder konfigurierten Sprache, die die Person spricht, selbst bei einem einzelnen Wort. Gilt nur, wenn additional_languages festgelegt ist
background_trackZeichenfolge | nullneinID für Hintergrundaudio oder null
acknowledgement_prompt_modeZeichenfolgeneinauto (Standard) oder manual (Storm-with-ack-Produkte)
acknowledgement_promptZeichenfolgeneinWird verwendet, wenn acknowledgement_prompt_mode="manual"
silence_interval_secondsGanzzahl | nullnein5–120. Sekunden der Stille des Anrufers vor einer Nachfrage
silence_max_checkinsGanzzahl | nullnein1–10
silence_checkins_enabledbooleschneinStandardmäßig true
connect_tone_enabledbooleschneinStandardmäßig false
pronunciation_dictsboolesch | nullneinStandardmäßig false; null entspricht false. Wenn true, verwendet die Cartesia-Sprachausgabe des Anrufs das Aussprachewörterbuch von ThunderPhone für die Sprache des Anrufs, sofern eines vorhanden ist. Funktioniert für Telefon- und Widget-Anrufe. Gilt nur für die eigenen Cartesia-Stimmen von ThunderPhone, nicht für Stimmen mit Ihrem eigenen Cartesia-API-Schlüssel. Wird nicht mit agent_id verwendet, da dort die Einstellungen des gespeicherten Agenten gelten
voicemail_actionZeichenfolgeneinprompt (Standard), hangup oder message
voicemail_messageZeichenfolgeneinWird verwendet, wenn voicemail_action="message"
agent_nameZeichenfolgeneinAnzeigename, der an Dashboards und das Widget gemeldet wird
org_nameZeichenfolgeneinAnzeigename der Organisation für die Persona des Agenten
toolsArrayneinSchemas für Inline-Funktionstools (siehe Funktionstools)
transfer_destinationsArray von Objekten | nullneinBis zu 50 Einträge {name, number}, an die dieser Anruf weitergeleitet werden kann, validiert wie die Weiterleitungsnummern eines Agenten. Der Agent leitet nach Namen und nur an diese weiter; [] bedeutet keine Weiterleitungen. Lassen Sie das Feld weg oder senden Sie null, damit der Agent an eine selbst gewählte Nummer weiterleiten kann
timezoneZeichenfolgeneinIANA-Zeitzone für diesen Anruf, z. B. America/Chicago. Der Agent nennt Datum und Uhrzeit in dieser Zeitzone, die Vorrang vor der eigenen Zeitzone des Agenten hat. Auch neben agent_id zulässig. Werte, die keine IANA-Zeitzonen sind, werden ignoriert. Siehe Zeitzone pro Anruf festlegen
call_idGanzzahlneinOptionales Echo der Call-ID der Anfrage; wird ignoriert

Da prompt und voice erforderlich sind, lehnt die Rückgabe von {} oder einer Antwort, die die Validierung nicht besteht, den Anruf ab: Ein Telefonanruf wird wie in Zeitüberschreitungen und Wiederholungsversuche beschrieben beendet, und eine Widget-Sitzungsanfrage schlägt mit 422 fehl. Für diesen Pfad gibt es keinen Fallback auf einen statischen Agenten (einer Nummer oder einem Schlüssel im Webhook-Modus ist kein Agent zugewiesen).


Begrenzung der Antwortgröße


Beispiel-Handler

Python (FastAPI)
import hashlib
import hmac
import json
import os
 
from fastapi import FastAPI, HTTPException, Request
 
app = FastAPI()
WEBHOOK_SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
 
def verify(body: bytes, signature: str) -> bool:
    expected = hmac.new(WEBHOOK_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"] == "telephony.incoming":
        caller = event["data"]["from_number"]
        prompt = (
            "Greet the caller as a San Francisco local…"
            if caller.startswith("+1415")
            else "You are a friendly customer support agent…"
        )
        return {
            "prompt": prompt,
            "voice": "john",
            "product": "spark",
        }
    if event["type"] == "web.incoming":
        return {
            "prompt": "You are the website's helpful voice assistant…",
            "voice": "john",
            "product": "spark",
        }
    return {}
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" }),
  (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));
 
    if (event.type === "telephony.incoming" || event.type === "web.incoming") {
      const caller = event.data.from_number || "web";
      const prompt = caller.startsWith("+1415")
        ? "Greet the caller as a San Francisco local…"
        : "You are a friendly customer support agent…";
      return res.json({
        prompt,
        voice: "john",
        product: "spark",
      });
    }
    res.json({});
  },
);

Antwort mit Funktionswerkzeugen

Fügen Sie Werkzeuge hinzu, damit die KI Ihre APIs während des Gesprächs aufrufen kann:

{
  "prompt":  "You are a booking assistant. Use the available tools to help customers schedule appointments.",
  "voice":   "john",
  "product": "spark",
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_appointments",
        "description": "Find available appointment slots",
        "parameters": {
          "type": "object",
          "properties": {
            "date": { "type": "string", "description": "YYYY-MM-DD" },
            "service": { "type": "string" }
          },
          "required": ["date"]
        }
      },
      "endpoint": {
        "url": "https://api.example.com/appointments/search",
        "method": "POST",
        "headers": {
          "X-Api-Key": "your-key"
        }
      }
    }
  ]
}

Produktstufen auf einen Blick

ProduktLatenzSchlussfolgernBestätigung
sparkNiedrigsteEinfach—
boltNiedrigVerbessert—
storm-baseMittelStark—
storm-base-with-ackMittelStarkAutomatischer Fülltext während des Nachdenkens
storm-extraHöherTiefgehend—
storm-extra-with-ackHöherTiefgehendAutomatischer Fülltext während des Nachdenkens

Verwandt