Open in
telephony.incoming / web.incoming
Webhook bloccante che definisce in tempo reale la configurazione di una chiamata in entrata.
Quando una chiamata telefonica in arrivo raggiunge un numero senza un
agente assegnato, oppure una sessione di widget web viene avviata su una chiave pubblicabile in
mode="webhook", ThunderPhone invia una richiesta bloccante
telephony.incoming / web.incoming al tuo
URL webhook legacy
e attende una risposta di configurazione: fino a 13 secondi per una
chiamata telefonica, incluso un tentativo aggiuntivo, e fino a 10 secondi per una sessione
del widget (vedi Timeout e tentativi aggiuntivi). 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 modello end-to-end.
Timeout e nuovi tentativi
Rispondi rapidamente: il chiamante è in attesa in linea mentre decidi.
Per una chiamata telefonica (telephony.incoming):
- Il primo tentativo può richiedere fino a 10 secondi.
- Se va in timeout, non riesce a connettersi o riceve una risposta
5xx,408o429, ThunderPhone prova un'altra volta. Il nuovo tentativo riceve il tempo rimanente di un budget di 13 secondi per l'intero scambio: circa 3 secondi dopo un primo tentativo andato in timeout, quasi tutti i 13 secondi dopo un errore rapido. Non viene effettuato alcun nuovo tentativo se rimarrebbero meno di 2 secondi. - Qualsiasi altra risposta
4xx, una risposta oltre il limite di dimensione o una2xxil cui corpo non contiene una configurazione valida è definitiva e non viene ritentata. - Entrambi i tentativi inviano lo stesso corpo, lo stesso
X-ThunderPhone-Signaturee la stessa intestazioneX-ThunderPhone-Idempotency-Key(telephony-incoming-<call_id>), così un gestore con effetti collaterali può riconoscere il nuovo tentativo.
Non è disponibile alcun agente di fallback. Se non arriva una configurazione utilizzabile, la
chiamata viene terminata subito anziché lasciare il chiamante su una linea
silenziosa. Nella cronologia chiamate appare come chiamata non fatturata non riuscita con
end_reason inbound_config_webhook_failed e viene inviato il consueto evento
telephony.complete.
Una sessione widget (web.incoming) riceve un solo tentativo fino a 10
secondi. Se non riesce, la richiesta della sessione non riesce con 502 o 422.
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 chiamata |
origin_domain | string | Origine della pagina che ospita il widget, come inviata dal browser. Un client non browser può inviare qualsiasi valore, quindi non trattarlo come prova della provenienza della richiesta |
publishable_key_prefix | string | Primi caratteri della chiave pubblicabile che ha aperto la sessione |
language, primary_language | string | Presente quando la sessione del widget ha richiesto un override della lingua |
voice | string | Presente quando la sessione del widget ha richiesto un override della voce |
website_context | string | Presente quando il widget ha passato il contesto della pagina per sessione |
I campi di override (language, voice, website_context e qualsiasi
variables del browser) arrivano solo quando la chiave pubblicabile li consente nei
suoi override del browser. Una
richiesta di sessione che contiene un override non consentito dalla chiave viene rifiutata prima che questo webhook venga chiamato.
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 della voce da GET /v1/voices, ad es. john. voice_name è accettato come alias. Le voci sconosciute non superano la convalida e rifiutano la chiamata |
product | string | no | Predefinito: spark. Valori 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* impone extra, gli altri storm-* impongono base |
audio_context_mode | string | no | full (predefinito) o reduced |
watchdog_enabled | boolean | no | Abilita la supervisione per questa chiamata. Predefinito: false |
additional_audio_context | boolean | null | no | Include gli ultimi turni dell'audio del chiamante anziché solo il turno più recente, migliorando le correzioni e la raccolta di dati ricchi 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 | Predefinito: false |
additional_languages | array di string | no | Lingue aggiuntive a cui l'agente può passare |
native_voice_switching | boolean | no | Predefinito: false. Quando la chiamata passa a un'altra lingua, sostituisce la voce configurata con una voce madrelingua per quella lingua, abbinata per genere |
language_switching | string | null | no | ask_first (predefinito) o follow_caller; null equivale a ask_first. Definisce come un agente multilingue gestisce l'altra persona che parla un'altra delle sue lingue. ask_first passa subito quando la persona richiede la lingua o pronuncia una frase completa in essa e chiede se continuare in quella lingua dopo qualcosa di breve, come un "sì". follow_caller passa a qualsiasi lingua configurata parlata dalla persona, anche per una sola parola. Si applica solo quando è impostato additional_languages |
background_track | string | null | no | ID dell'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 richiesta di conferma |
silence_max_checkins | integer | null | no | 1–10 |
silence_checkins_enabled | boolean | no | Predefinito: true |
connect_tone_enabled | boolean | no | Predefinito: false |
pronunciation_dicts | boolean | null | no | Predefinito: false; null equivale a false. Quando è true, la sintesi vocale Cartesia della chiamata utilizza il dizionario di pronuncia ThunderPhone per la lingua della chiamata, se disponibile. Funziona per le chiamate telefoniche e del widget. Si applica solo alle voci Cartesia di ThunderPhone, non alle voci associate alla tua chiave API Cartesia. Non viene utilizzato con agent_id, per il quale si applicano le impostazioni dell'agente salvato |
voicemail_action | string | no | prompt (predefinito), hangup o message |
voicemail_message | string | no | Utilizzato quando voicemail_action="message" |
agent_name | string | no | Nome visualizzato riportato nelle dashboard e nel 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) |
transfer_destinations | array di object | null | no | Fino a 50 voci {name, number} a cui questa chiamata può essere trasferita, convalidate come i numeri di trasferimento di un agente. L'agente trasferisce in base al nome e solo verso queste destinazioni; [] indica nessun trasferimento. Ometti il campo o invia null per consentire all'agente di trasferire a un numero scelto da lui |
timezone | string | no | Fuso orario IANA per questa chiamata, ad esempio America/Chicago. L'agente comunica la data e l'ora in questo fuso, anziché nel proprio fuso orario. Consentito anche accanto a agent_id. I valori che non sono fusi orari IANA vengono ignorati. Vedi Imposta il fuso orario per chiamata |
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: una chiamata telefonica viene terminata
come descritto in Timeout e tentativi, e una
richiesta di sessione del widget non va a buon fine con 422. Non è disponibile alcun fallback
a un 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.