Configurare dinamică pentru fiecare apel
Alegeți agentul care răspunde — sau rescrieți-i instrucțiunile și setările — separat pentru fiecare apel primit, pe baza unei logici personalizate dintr-un webhook pe care îl controlați.
În mod implicit, fiecărui număr de telefon și fiecărei chei publicabile îi este atribuit un agent static. Când aveți nevoie de personalizare pentru fiecare apelant sau pentru fiecare vizitator — rutare VIP, context pentru utilizatorii autentificați, teste A/B pentru prompturi — treceți la modul webhook și lăsați serverul să decidă.
Cum funcționează
- Vă abonați la evenimentul
telephony.incoming(telefon) sauweb.incoming(widget). Ambele sunt webhookuri blocante: ThunderPhone așteaptă până la 10 secunde răspunsul dumneavoastră înainte de a continua apelul. - ThunderPhone vă trimite
{call_id, from_number, to_number}(sesiunile widget includ câmpuri specifice widgetului în locul numerelor — consultați schema solicitării). - Serverul dumneavoastră răspunde cu o configurație de agent (prompt, voce, produs, instrumente). ThunderPhone utilizează acea configurație pentru apel.
- Dacă returnați
{}, expiră timpul de răspuns sau apare o eroare, agentul atribuit static este utilizat ca rezervă. O valoare implicită sigură.
1. Configurați destinația webhook
Pentru numere de telefon, abonați endpointul dumneavoastră la 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"]
}'Răspunsul include un secret de unică folosință — salvați-l; îl veți utiliza
pentru verificarea semnăturii.
Pentru sesiunile widget, creați o cheie publicabilă în mode="webhook"
cu URL-ul endpointului inclus:
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"]
}'Widgetul va trimite o solicitare POST către acest URL la începutul fiecărei sesiuni.
2. Implementați gestionarul
Trei reguli practice:
- Verificați semnătura pentru fiecare cerere (consultați Verificarea semnăturilor webhook). Nu omiteți acest pas în dezvoltare — implementați-l corect o dată și reutilizați-l.
- Răspundeți rapid. Zece secunde reprezintă limita strictă, iar fiecare secundă este tăcere pentru apelant. Efectuați căutări în baza de date dacă este necesar, dar nu apelați LLM-uri din aval sincron — dacă doriți generarea dinamică a prompturilor, precalculați și stocați în cache.
- Folosiți o revenire curată. Orice stare neașteptată trebuie să returneze
{}, astfel încât agentul atribuit static să gestioneze apelul.
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 răspunsului
Corpul răspunsului corespunde exact schema de răspuns pentru apeluri primite. Câmpurile utilizate frecvent:
| Câmp | Tip | Descriere |
|---|---|---|
prompt | șir de caractere (obligatoriu) | Prompt de sistem pentru agent |
voice | șir de caractere (obligatoriu) | ID vocal din GET /v1/voices |
product | șir de caractere | Valoarea implicită este spark |
background_track | șir de caractere | null | ID audio ambiental |
acknowledgement_prompt_mode | șir de caractere | auto sau manual (numai Storm-cu-confirmare) |
acknowledgement_prompt | șir de caractere | Obligatoriu când modul este manual |
tools | matrice | Scheme inline pentru instrumente de funcții — consultați Instrumente de funcții |
Modele
Contextul utilizatorului autentificat
În widgeturile în modul webhook, pagina vizitatorului știe deja cine este acesta.
Apelați webhookul cu un parametru de șir de interogare pe care SDK-ul widgetului
îl redirecționează (?customer_id=123) și căutați clientul pe server.
Lansare A/B a prompturilor
Înainte de a implementa manual acest lucru, rețineți că ThunderPhone are o funcție nativă
Experimente
(/dashboard/experiments și fila A/B din generatorul de agenți) care
definește variante, distribuie traficul și compară rezultatele pentru fiecare variantă —
nu este necesar niciun webhook.
Dacă aveți totuși nevoie de control din partea webhookului: calculați hash-ul pentru call_id → compartiment;
serviți promptul A pentru 0..49 și promptul B pentru 50..99. Înregistrați compartimentul
ales în propria bază de date și corelați-l ulterior cu evaluarea apelului finalizat.
Rutare bazată pe timp
Program de lucru → agent pentru „asistență în direct”; în afara programului → agent pentru „preluare mesaj”.
Comutare simplă pe new Date().getUTCHours() în handlerul dumneavoastră.
Pașii următori
Scheme exacte pentru cereri și răspunsuri, inclusiv fiecare cheie de configurare.
Configurați corect HMAC o dată; reutilizați-l peste tot.
Combinați rutarea dinamică cu instrumente per agent.
Reîncercări, ordonare, expirări.