Configurazione dinamica per chiamata
Scegli l
Per impostazione predefinita, a ogni numero di telefono e chiave pubblicabile è assegnato un agente statico. Quando ti serve una personalizzazione per chiamante o per visitatore — instradamento VIP, contesto degli utenti autenticati, test A/B dei prompt — passa alla modalità webhook e lascia decidere al tuo server.
Come funziona
- Ti iscrivi all'evento
telephony.incoming(telefono) oweb.incoming(widget). Entrambi sono webhook bloccanti: ThunderPhone attende fino a 10 secondi la tua risposta prima di proseguire la chiamata. - ThunderPhone ti invia
{call_id, from_number, to_number}(le sessioni widget includono campi specifici del widget anziché numeri — consulta lo schema della richiesta). - Il tuo server risponde con una configurazione dell'agente (prompt, voce, prodotto, strumenti). ThunderPhone usa quella configurazione per la chiamata.
- Se restituisci
{}, vai in timeout o si verifica un errore, viene usato come fallback l'agente assegnato staticamente. Un'impostazione predefinita sicura.
1. Configura la destinazione webhook
Per i numeri di telefono, iscrivi il tuo endpoint a telephony.incoming:
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"label": "Prod call-incoming",
"url": "https://example.com/thunderphone/incoming",
"events": ["telephony.incoming"]
}'La risposta include un secret monouso — salvalo; lo userai
per la verifica della firma.
Per le sessioni widget, crea una chiave pubblicabile in mode="webhook"
con l'URL del tuo endpoint incorporato:
curl -X POST https://api.thunderphone.com/v1/publishable-key \
-H "Authorization: Bearer sk_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Dynamic widget",
"mode": "webhook",
"webhook_url": "https://example.com/thunderphone/widget-incoming",
"allowed_domains": ["example.com"]
}'Il widget invierà una richiesta POST a questo URL all'avvio di ogni sessione.
2. Implementa il gestore
Tre regole pratiche:
- Verifica la firma su ogni richiesta (vedi Verifica le firme dei webhook). Non saltare questo passaggio in sviluppo: fallo correttamente una volta e riutilizzalo.
- Rispondi rapidamente. Dieci secondi sono il limite massimo, e ogni secondo è silenzio per il chiamante. Esegui ricerche nel database se necessario, ma non chiamare LLM downstream in modo sincrono: se vuoi la generazione dinamica dei prompt, precalcola e memorizza nella cache.
- Applica un fallback pulito. Qualsiasi stato imprevisto deve restituire
{}affinché l'agente assegnato staticamente gestisca la chiamata.
import hashlib
import hmac
import json
import os
from fastapi import FastAPI, HTTPException, Request
app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]
def verify(body: bytes, sig: str) -> bool:
expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig or "")
@app.post("/thunderphone/incoming")
async def incoming(request: Request):
body = await request.body()
if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
raise HTTPException(401)
event = json.loads(body)
if event["type"] not in ("telephony.incoming", "web.incoming"):
return {} # fall back to default
caller = event["data"]["from_number"]
# Cheap DB lookup: is this a known VIP?
customer = lookup_customer(caller)
if customer and customer.tier == "vip":
return {
"prompt": f"You are a VIP concierge for {customer.name}. Be proactive…",
"voice": "john",
"product": "storm-base",
}
return {} # default agent handles non-VIPs
def lookup_customer(phone: str):
# ... your CRM integration ...
passimport crypto from "node:crypto";
import express from "express";
const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;
function verify(body, sig) {
const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
return sig &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}
app.post(
"/thunderphone/incoming",
express.raw({ type: "application/json" }),
async (req, res) => {
if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
const IMPORTANT_TYPES = new Set([
"telephony.incoming",
"web.incoming",
]);
if (!IMPORTANT_TYPES.has(event.type)) return res.json({});
const customer = await lookupCustomer(event.data.from_number);
if (customer?.tier === "vip") {
return res.json({
prompt: `You are a VIP concierge for ${customer.name}. Be proactive…`,
voice: "john",
product: "storm-base",
});
}
res.json({}); // fall back to default agent
},
);3. Schema della risposta
Il corpo della risposta corrisponde esattamente allo schema della risposta per chiamate in arrivo. I campi usati più comunemente:
| Campo | Tipo | Descrizione |
|---|---|---|
prompt | stringa (obbligatorio) | Prompt di sistema per l'agente |
voice | stringa (obbligatorio) | ID voce da GET /v1/voices |
product | stringa | Il valore predefinito è spark |
background_track | stringa | null | ID dell'audio ambientale |
acknowledgement_prompt_mode | stringa | auto o manual (solo Storm-with-ack) |
acknowledgement_prompt | stringa | Obbligatorio quando la modalità è manual |
tools | array | Schemi inline per strumenti funzione: vedi Strumenti funzione |
Modelli
Contesto dell'utente autenticato
Nei widget in modalità webhook, la pagina del visitatore sa già chi
è. Chiama il tuo webhook con un parametro query string che l'SDK del
widget inoltra (?customer_id=123) e cerca il cliente lato server.
Distribuzione A/B dei prompt
Prima di implementarlo manualmente, tieni presente che ThunderPhone offre una funzionalità nativa di
Esperimenti
(/dashboard/experiments e la scheda A/B del builder dell'agente) che
definisce le varianti, suddivide il traffico e confronta i risultati per variante —
senza webhook.
Se hai comunque bisogno del controllo lato webhook: applica un hash a call_id → bucket;
fornisci il prompt A per 0..49 e il prompt B per 50..99. Registra nel tuo DB
il bucket scelto e in seguito correla il dato con la valutazione della chiamata
completata.
Instradamento basato sull'orario
Orario di apertura → agente di "assistenza dal vivo"; fuori orario → agente per
"prendere un messaggio". Semplice switch su new Date().getUTCHours() nel tuo handler.
Passaggi successivi
Schemi esatti di richiesta e risposta, incluse tutte le chiavi di configurazione.
Configura correttamente l'HMAC una volta; riutilizzalo ovunque.
Combina l'instradamento dinamico con strumenti per agente.
Riprova, ordinamento, timeout.