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"
}
}| Feld | Typ | Beschreibung |
|---|---|---|
call_id | integer | Anruf-ID — über alle Ereignisse für diesen Anruf hinweg stabil |
from_number | string | E.164-Nummer des Anrufers |
to_number | string | E.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"
}
}| Feld | Typ | Beschreibung |
|---|---|---|
call_id | integer | Anruf-ID |
origin_domain | string | Der Seitenursprung, der das Widget hostet |
publishable_key_prefix | string | Erste Zeichen des veröffentlichbaren Schlüssels, der die Sitzung geöffnet hat |
language, primary_language | string | Vorhanden, wenn die Widget-Sitzung eine Sprachüberschreibung angefordert hat |
voice | string | Vorhanden, wenn die Widget-Sitzung eine Stimmüberschreibung angefordert hat |
website_context | string | Vorhanden, 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": []
}| Feld | Typ | Erforderlich | Beschreibung |
|---|---|---|---|
prompt | string | ja | System-Prompt, der den Agenten steuert |
voice | string | ja | Sprach-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 |
product | string | nein | Standardwert ist spark. Zulässig: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | nein | minimal, base (Standard) oder extra. Für Storm-Produkte überschrieben: storm-extra* erzwingt extra, andere storm-* erzwingen base |
audio_context_mode | string | nein | full (Standard) oder reduced |
watchdog_enabled | boolean | nein | Überwachung für diesen Anruf aktivieren. Standardwert false |
additional_audio_context | boolean | null | nein | Die 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_mode | string | nein | none, acknowledgement (Standard) oder tick |
language | string | nein | Kurzform für primary_language |
primary_language | string | nein | Sprachcode, normalisiert (Standard en). Nicht auflösbare Codes lehnen den Anruf ab |
has_additional_languages | boolean | nein | Standardwert false |
additional_languages | array of string | nein | Zusätzliche Sprachen, zu denen der Agent wechseln kann |
native_voice_switching | boolean | nein | Standardwert 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_track | string | null | nein | ID für Hintergrundaudio oder null |
acknowledgement_prompt_mode | string | nein | auto (Standard) oder manual (Storm-with-ack-Produkte) |
acknowledgement_prompt | string | nein | Wird verwendet, wenn acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | nein | 5–120. Sekunden der Stille des Anrufers vor einer Rückfrage |
silence_max_checkins | integer | null | nein | 1–10 |
silence_checkins_enabled | boolean | nein | Standardwert true |
connect_tone_enabled | boolean | nein | Standardwert false |
voicemail_action | string | nein | prompt (Standard), hangup oder message |
voicemail_message | string | nein | Wird verwendet, wenn voicemail_action="message" |
agent_name | string | nein | Anzeigename, der an Dashboards und das Widget übermittelt wird |
org_name | string | nein | Organisations-Anzeigename für die Persona des Agenten |
tools | array | nein | Inline-Schemas für Funktionstools (siehe Funktionstools) |
call_id | integer | nein | Optionales 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
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 {}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
| Produkt | Latenz | Schlussfolgern | Bestätigung |
|---|---|---|---|
spark | Niedrigste | Einfach | — |
bolt | Niedrig | Verbessert | — |
storm-base | Mittel | Stark | — |
storm-base-with-ack | Mittel | Stark | Automatischer Fülltext während des Nachdenkens |
storm-extra | Höher | Tiefgehend | — |
storm-extra-with-ack | Höher | Tiefgehend | Automatischer Fülltext während des Nachdenkens |
Verwandt
Das nicht blockierende Ereignis am Ende eines Anrufs.
Vollständiges JSON-Schema für tools[] und den Vertrag für signierte Endpunkte.
Mehrere URLs für telephony.incoming / web.incoming abonnieren.
Muster für anruferspezifische Prompts, Tools und A/B-Tests.