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 Telefonanruf 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 Anfrage telephony.incoming / web.incoming an Ihre Legacy-Webhook-URL und wartet bis zu 10 Sekunden auf eine Konfigurationsantwort. Nutzen Sie diesen Austausch, um Prompt, Stimme und Tools für jeden Anruf dynamisch auszuwählen — siehe den Leitfaden zur dynamischen Anrufkonfiguration für das End-to-End-Muster.

Der blockierende Austausch hat keinen Fallback: Wenn Ihr Handler einen Nicht-2xx-Status zurückgibt, das Zeitlimit überschreitet oder eine Konfiguration zurückgibt, die die Validierung nicht besteht, wird der Anruf abgelehnt (der Telefonanruf wird nicht verbunden; die Anfrage der Widget-Sitzung schlägt mit 502/422 fehl). Antworten Sie schnell — der Anrufer hört während Ihrer Entscheidung das Freizeichen.

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_idintegerAnruf-ID
origin_domainstringDer Seitenursprung, der das Widget hostet
publishable_key_prefixstringErste Zeichen des veröffentlichbaren Schlüssels, der die Sitzung geöffnet hat
language, primary_languagestringVorhanden, wenn die Widget-Sitzung eine Sprachüberschreibung angefordert hat
voicestringVorhanden, wenn die Widget-Sitzung eine Stimmüberschreibung angefordert hat
website_contextstringVorhanden, wenn das Widget seitenbezogenen Kontext pro Sitzung übergeben hat

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
promptstringjaSystem-Prompt, der den Agenten steuert
voicestringjaSprach-ID von GET /v1/voices, z. B. john. voice_name wird als Alias akzeptiert. Unbekannte Stimmen schlagen bei der Validierung fehl und lehnen den Anruf ab
productstringneinStandardwert ist spark. Zulässig: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringneinminimal, base (Standard) oder extra. Für Storm-Produkte überschrieben: storm-extra* erzwingt extra, andere storm-* erzwingen base
audio_context_modestringneinfull (Standard) oder reduced
watchdog_enabledbooleanneinÜberwachung für diesen Anruf aktivieren. Standardwert false
additional_audio_contextboolean | nullneinDie letzten Anrufer-Audioabschnitte statt nur des neuesten Abschnitts einbeziehen. Dies verbessert Korrekturen sowie die Erfassung von Daten mit vielen Buchstaben oder Zahlen bei geringfügig höherer Latenz und Kosten. Für eingehende Sitzungen standardmäßig aktiviert und für ausgehende Telefonanrufe deaktiviert; null behält den Standardwert bei
storm_feedback_modestringneinnone, acknowledgement (Standard) oder tick
languagestringneinKurzform für primary_language
primary_languagestringneinSprachcode, normalisiert (Standard en). Nicht auflösbare Codes lehnen den Anruf ab
has_additional_languagesbooleanneinStandardwert false
additional_languagesarray of stringneinZusätzliche Sprachen, zu denen der Agent wechseln kann
native_voice_switchingbooleanneinStandardwert false. Wenn der Anruf zu einer anderen Sprache wechselt, wird zu einer in dieser Sprache nativen Stimme gewechselt (nach Geschlecht abgeglichen), statt die konfigurierte Stimme beizubehalten
background_trackstring | nullneinID für Hintergrundaudio oder null
acknowledgement_prompt_modestringneinauto (Standard) oder manual (Storm-with-ack-Produkte)
acknowledgement_promptstringneinWird verwendet, wenn acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullnein5–120. Sekunden der Stille des Anrufers vor einer Rückfrage
silence_max_checkinsinteger | nullnein1–10
silence_checkins_enabledbooleanneinStandardwert true
connect_tone_enabledbooleanneinStandardwert false
voicemail_actionstringneinprompt (Standard), hangup oder message
voicemail_messagestringneinWird verwendet, wenn voicemail_action="message"
agent_namestringneinAnzeigename, der an Dashboards und das Widget übermittelt wird
org_namestringneinOrganisations-Anzeigename für die Persona des Agenten
toolsarrayneinInline-Schemas für Funktionstools (siehe Funktionstools)
call_idintegerneinOptionales Echo der Anruf-ID der Anfrage; wird ignoriert

Da prompt und voice erforderlich sind, lehnt die Rückgabe von {} oder jede Antwort, die die Validierung nicht besteht, den Anruf mit 422 ab — es gibt auf diesem Pfad 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