telephony.incoming / web.incoming
Webhook bloccante che definisce in tempo reale la configurazione di una chiamata in entrata.
Quando una chiamata telefonica in entrata raggiunge un numero senza un
agente assegnato, oppure una sessione del widget web viene avviata su una chiave
pubblicabile in mode="webhook", ThunderPhone invia una richiesta
telephony.incoming / web.incoming bloccante al tuo
URL webhook legacy
e attende fino a 10 secondi una risposta di configurazione. Usa questo
scambio per scegliere dinamicamente un prompt, una voce e gli strumenti per ogni chiamata —
consulta la guida alla configurazione dinamica delle chiamate
per il flusso completo.
Lo scambio bloccante non ha fallback: se il tuo handler restituisce uno
stato non 2xx, supera il timeout o restituisce una configurazione che non supera
la convalida, la chiamata viene rifiutata (la chiamata telefonica non si connette;
la richiesta della sessione widget non riesce con 502/422). Rispondi rapidamente —
il chiamante sente il tono di chiamata mentre decidi.
Payload della richiesta
Per le chiamate telefoniche (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Campo | Tipo | Descrizione |
|---|---|---|
call_id | integer | ID della chiamata — stabile in tutti gli eventi di questa chiamata |
from_number | string | Numero del chiamante in formato E.164 |
to_number | string | Destinazione E.164 (uno dei tuoi numeri ThunderPhone) |
Per le sessioni del widget web (web.incoming), data identifica la
pagina di incorporamento anziché i numeri telefonici:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Campo | Tipo | Descrizione |
|---|---|---|
call_id | integer | ID della chiamata |
origin_domain | string | L'origine della pagina che ospita il widget |
publishable_key_prefix | string | Primi caratteri della chiave pubblicabile che ha aperto la sessione |
language, primary_language | string | Presente quando la sessione widget ha richiesto un override della lingua |
voice | string | Presente quando la sessione widget ha richiesto un override della voce |
website_context | string | Presente quando il widget ha passato il contesto della pagina per sessione |
Schema della risposta
Restituisci un oggetto JSON che descriva la configurazione dell'agente per questa chiamata.
prompt e voice sono obbligatori; tutto il resto è facoltativo.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Campo | Tipo | Obbligatorio | Descrizione |
|---|---|---|---|
prompt | string | sì | Prompt di sistema che guida l'agente |
voice | string | sì | ID voce da GET /v1/voices, ad esempio john. voice_name è accettato come alias. Le voci sconosciute non superano la convalida e rifiutano la chiamata |
product | string | no | Il valore predefinito è spark. Consentiti: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | no | minimal, base (predefinito) o extra. Sovrascritto per i prodotti Storm: storm-extra* forza extra, gli altri storm-* forzano base |
audio_context_mode | string | no | full (predefinito) o reduced |
watchdog_enabled | boolean | no | Abilita la supervisione per questa chiamata. Valore predefinito false |
additional_audio_context | boolean | null | no | Includi gli ultimi turni dell'audio del chiamante anziché solo il turno più recente, migliorando le correzioni e la raccolta di dati ricca di ortografia/numeri con un lieve impatto su latenza e costo. È attivo per impostazione predefinita nelle sessioni in entrata e disattivo nelle chiamate telefoniche in uscita; null mantiene il valore predefinito |
storm_feedback_mode | string | no | none, acknowledgement (predefinito) o tick |
language | string | no | Abbreviazione di primary_language |
primary_language | string | no | Codice lingua, normalizzato (predefinito en). I codici non risolvibili rifiutano la chiamata |
has_additional_languages | boolean | no | Valore predefinito false |
additional_languages | array di string | no | Lingue aggiuntive a cui l'agente può passare |
native_voice_switching | boolean | no | Valore predefinito false. Quando la chiamata passa a un'altra lingua, sostituisci la voce con una voce nativa di quella lingua (abbinata per genere) anziché mantenere la voce configurata |
background_track | string | null | no | ID audio ambientale o null |
acknowledgement_prompt_mode | string | no | auto (predefinito) o manual (prodotti Storm-with-ack) |
acknowledgement_prompt | string | no | Utilizzato quando acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | no | 5–120. Secondi di silenzio del chiamante prima di una verifica |
silence_max_checkins | integer | null | no | 1–10 |
silence_checkins_enabled | boolean | no | Valore predefinito true |
connect_tone_enabled | boolean | no | Valore predefinito false |
voicemail_action | string | no | prompt (predefinito), hangup o message |
voicemail_message | string | no | Utilizzato quando voicemail_action="message" |
agent_name | string | no | Nome visualizzato segnalato alle dashboard e al widget |
org_name | string | no | Nome visualizzato dell'organizzazione per la persona dell'agente |
tools | array | no | Schemi inline degli strumenti funzione (vedi Strumenti funzione) |
call_id | integer | no | Eco facoltativo dell'ID chiamata della richiesta; ignorato |
Poiché prompt e voice sono obbligatori, restituire {} o qualsiasi
risposta che non superi la convalida rifiuta la chiamata con 422: non esiste
un fallback dell'agente statico in questo percorso (un numero o una chiave in modalità webhook
non ha un agente assegnato).
Limite delle dimensioni della risposta
Esempio di gestore
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({});
},
);Risposta con strumenti funzione
Allega strumenti affinché l'IA possa chiamare le tue API durante la conversazione:
{
"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"
}
}
}
]
}Scheda rapida dei livelli di prodotto
| Prodotto | Latenza | Ragionamento | Conferma |
|---|---|---|---|
spark | Minima | Base | — |
bolt | Bassa | Migliorato | — |
storm-base | Media | Solido | — |
storm-base-with-ack | Media | Solido | Riempitivo automatico durante l'elaborazione |
storm-extra | Più alta | Approfondito | — |
storm-extra-with-ack | Più alta | Approfondito | Riempitivo automatico durante l'elaborazione |
Correlati
L'evento di fine chiamata non bloccante.
Schema JSON completo per tools[] e contratto dell'endpoint firmato.
Iscrivi più URL a telephony.incoming / web.incoming.
Modelli per prompt, strumenti e test A/B per chiamante.