Open in
telephony.incoming / web.incoming
Webhook blocant care configurează în timp real o apelare de intrare.
Când un apel telefonic primit ajunge la un număr fără un agent
atribuit sau când o sesiune de widget web începe pe o cheie publicabilă în
mode="webhook", ThunderPhone trimite o solicitare blocantă
telephony.incoming / web.incoming către
adresa URL webhook veche
și așteaptă un răspuns de configurare: până la 13 secunde pentru un
apel telefonic, inclusiv o reîncercare, și până la 10 secunde pentru o
sesiune de widget (consultați Expirări și reîncercări). Utilizați acest
schimb pentru a alege dinamic un prompt, o voce și instrumente pentru fiecare apel —
consultați ghidul de configurare dinamică a apelurilor
pentru modelul complet.
Expirări de timp și reîncercări
Răspundeți rapid: apelantul așteaptă pe linie în timp ce decideți.
Pentru un apel telefonic (telephony.incoming):
- Prima încercare poate dura până la 10 secunde.
- Dacă expiră, nu se poate conecta sau primește un răspuns
5xx,408ori429, ThunderPhone încearcă încă o dată. Reîncercarea primește timpul rămas dintr-un buget de 13 secunde pentru întregul schimb: aproximativ 3 secunde după o primă încercare care a expirat, aproape toate cele 13 secunde după un eșec rapid. Nu există reîncercare dacă ar rămâne mai puțin de 2 secunde. - Orice alt răspuns
4xx, un răspuns care depășește limita de dimensiune sau un răspuns2xxal cărui corp nu este o configurație validă este final și nu este reîncercat. - Ambele încercări trimit același corp, același antet
X-ThunderPhone-Signatureși același antetX-ThunderPhone-Idempotency-Key(telephony-incoming-<call_id>), astfel încât un handler cu efecte secundare poate recunoaște reîncercarea.
Nu există agent de rezervă. Dacă nu sosește o configurație utilizabilă,
apelul este încheiat imediat, în loc ca apelantul să rămână pe o linie
silențioasă. Acesta apare în istoricul apelurilor ca un apel eșuat, nefacturat, cu
end_reason inbound_config_webhook_failed, iar evenimentul obișnuit
telephony.complete este trimis.
O sesiune de widget (web.incoming) primește o singură încercare de până la 10
secunde. Dacă aceasta eșuează, solicitarea sesiunii eșuează cu 502 sau 422.
Încărcătura solicitării
Pentru apelurile telefonice (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Câmp | Tip | Descriere |
|---|---|---|
call_id | integer | ID-ul apelului — stabil în toate evenimentele pentru acest apel |
from_number | string | Numărul E.164 al apelantului |
to_number | string | Destinația E.164 (unul dintre numerele dumneavoastră ThunderPhone) |
Pentru sesiunile widgetului web (web.incoming), data identifică
pagina de încorporare în locul numerelor de telefon:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Câmp | Tip | Descriere |
|---|---|---|
call_id | integer | ID apel |
origin_domain | string | Originea paginii care găzduiește widgetul, așa cum a transmis-o browserul. Un client care nu este browser poate trimite orice valoare, așadar nu o tratați drept dovadă a originii solicitării |
publishable_key_prefix | string | Primele caractere ale cheii publicabile care a deschis sesiunea |
language, primary_language | string | Prezente când sesiunea widgetului a solicitat o suprascriere a limbii |
voice | string | Prezent când sesiunea widgetului a solicitat o suprascriere a vocii |
website_context | string | Prezent când widgetul a transmis contextul paginii pentru fiecare sesiune |
Câmpurile de suprascriere (language, voice, website_context și orice
variables din browser) sosesc numai când cheia publicabilă le permite în
suprascrierile din browser. O
solicitare de sesiune care conține una pe care cheia nu o permite este respinsă înainte
ca acest webhook să fie apelat.
Schema de răspuns
Returnați un obiect JSON care descrie configurația agentului pentru acest apel.
prompt și voice sunt obligatorii; toate celelalte câmpuri sunt opționale.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Câmp | Tip | Obligatoriu | Descriere |
|---|---|---|---|
prompt | șir | da | Promptul de sistem care ghidează agentul |
voice | șir | da | ID-ul vocii din GET /v1/voices, de exemplu john. voice_name este acceptat ca alias. Vocile necunoscute nu trec validarea și resping apelul |
product | șir | nu | Valoarea implicită este spark. Permise: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | șir | nu | minimal, base (implicit) sau extra. Suprascris pentru produsele Storm: storm-extra* impune extra, iar alte storm-* impun base |
audio_context_mode | șir | nu | full (implicit) sau reduced |
watchdog_enabled | boolean | nu | Activați supravegherea pentru acest apel. Valoarea implicită este false |
additional_audio_context | boolean | null | nu | Includeți ultimele câteva schimburi de audio ale apelantului, în loc doar de cel mai recent schimb, îmbunătățind corecturile și colectarea de date cu multe litere sau numere, cu un mic cost suplimentar de latență și preț. Este activat implicit pentru sesiunile de intrare și dezactivat pentru apelurile telefonice de ieșire; null păstrează valoarea implicită |
storm_feedback_mode | șir | nu | none, acknowledgement (implicit) sau tick. De asemenea, ce aude apelantul în timp ce modelele audio procesează un schimb atunci când audio_tier este hearing |
language | șir | nu | Formă prescurtată pentru primary_language |
primary_language | șir | nu | Cod de limbă, normalizat (implicit en). Codurile care nu pot fi rezolvate resping apelul |
has_additional_languages | boolean | nu | Valoarea implicită este false |
additional_languages | matrice de șiruri | nu | Limbi suplimentare la care agentul poate trece |
native_voice_switching | boolean | nu | Valoarea implicită este false. Când apelul trece la altă limbă, înlocuiți vocea configurată cu o voce nativă pentru limba respectivă (potrivită după gen) |
language_switching | șir | null | nu | ask_first (implicit) sau follow_caller; null este echivalent cu ask_first. Modul în care un agent multilingv gestionează situația în care cealaltă persoană vorbește o altă limbă a sa. ask_first trece imediat când persoana cere limba respectivă sau spune o propoziție completă în ea și întreabă dacă să continue în acea limbă după ceva scurt, cum ar fi un „da”. follow_caller trece la orice limbă configurată pe care o vorbește persoana, chiar și pentru un singur cuvânt. Se aplică numai când este definit additional_languages |
audio_tier | șir | null | nu | Numai pentru product: "bolt". standard (implicit; "" și null au același sens) rulează trei transcriitori (apelurile care procesează PHI folosesc numai transcriitori acoperiți de BAA). lean reduce la un singur transcriitor, care aude greșit mai des. hearing adaugă la standard un ansamblu puternic de modele audio care ascultă audio-ul apelantului în unele schimburi, care durează cu aproximativ 1–2 secunde mai mult. Alte produse ignoră această opțiune |
endpointing | șir | null | nu | faster (200 ms), fast (300 ms), normal (500 ms, implicit) sau patient (800 ms); null este echivalent cu normal. Cât timp așteaptă un agent Bolt v2 după ce apelantul se oprește înainte de a cere modelului un răspuns. Așteptările mai scurte pot răspunde mai repede, dar trimit mai multe cereri pe care următoarele cuvinte ale apelantului le fac depășite, ceea ce crește costul modelului. Alte produse ignoră această opțiune |
eagerness | șir | null | nu | Depreciat: denumirea lui endpointing până la 19 octombrie 2026, încă citită. Este ignorat când este definit endpointing |
intelligence | șir | null | nu | express, lite sau standard (implicit); null este echivalent cu standard. Nivelul de inteligență al unui agent bolt pe fluxul Bolt v2: ce model îi scrie răspunsurile. express are aproximativ 600 ms până la primul cuvânt, este mai ieftin decât Standard și urmează instrucțiunile cu cea mai mică fiabilitate; lite are aproximativ 900 ms, este cel mai ieftin și mai puțin fiabil; standard are aproximativ 900 ms și urmează cel mai bine instrucțiunile. Răspunsurile Express și Lite care ar apela doar un instrument sunt predate modelului Standard. Alte produse ignoră această opțiune |
background_track | șir | null | nu | ID de audio ambiental sau null |
acknowledgement_prompt_mode | șir | nu | auto (implicit) sau manual (produse Storm-with-ack) |
acknowledgement_prompt | șir | nu | Utilizat când acknowledgement_prompt_mode="manual" |
silence_interval_seconds | întreg | null | nu | 5–120. Secunde de tăcere ale apelantului înainte de o verificare |
silence_max_checkins | întreg | null | nu | 1–10 |
silence_checkins_enabled | boolean | nu | Valoarea implicită este true |
silence_final_action | șir | nu | După ultima verificare: hang_up (implicit) sau transfer către silence_transfer_destination |
silence_transfer_destination | șir | nu | Numele unei intrări din transfer_destinations al acestei configurații; obligatoriu cu transfer. Fără transfer_destinations, transferul nu poate fi efectuat, iar agentul închide apelul |
silence_final_message | șir | nu | ≤ 300 de caractere, citit exact cum este scris; "" (implicit) folosește mesajul implicit |
connect_tone_enabled | boolean | nu | Valoarea implicită este false |
pronunciation_dicts | boolean | null | nu | Valoarea implicită este false; null este echivalent cu false. Când este true, vocea Cartesia a apelului utilizează dicționarul de pronunție ThunderPhone pentru limba apelului, acolo unde există. Funcționează pentru apeluri telefonice și prin widget. Se aplică numai vocilor Cartesia proprii ThunderPhone, nu vocilor de pe propria cheie API Cartesia. Nu este utilizat cu agent_id, unde se aplică setările agentului salvat |
voicemail_action | șir | nu | prompt (implicit), hangup sau message |
voicemail_message | șir | nu | Utilizat când voicemail_action="message" |
agent_name | șir | nu | Nume afișat raportat în tablourile de bord și în widget |
org_name | șir | nu | Numele afișat al organizației pentru persona agentului |
tools | matrice | nu | Scheme de instrumente de funcții inline (consultați Instrumente de funcții) |
transfer_destinations | matrice de obiecte | null | nu | Până la 50 de intrări {name, number} către care acest apel poate fi transferat, validate ca numerele de transfer ale unui agent. Agentul transferă după nume și numai către acestea; [] înseamnă fără transferuri. Omiteți-l sau trimiteți null pentru a permite agentului să transfere către un număr ales de acesta |
timezone | șir | nu | Fus orar IANA pentru acest apel, cum ar fi America/Chicago. Agentul indică data și ora în acel fus, în locul propriului fus orar al agentului. Este permis și alături de agent_id. Valorile care nu sunt zone IANA sunt ignorate. Consultați Setarea fusului orar pentru fiecare apel |
call_id | întreg | nu | Ecou opțional al ID-ului apelului din cerere; ignorat |
Deoarece prompt și voice sunt obligatorii, returnarea {} sau a oricărui răspuns care nu trece validarea respinge apelul: un apel telefonic este încheiat conform descrierii din Expirări și reîncercări, iar o solicitare de sesiune pentru widget eșuează cu 422. Nu există o rezervă către un agent static pe această cale (un număr sau o cheie în modul webhook nu are un agent atribuit).
Limită de dimensiune a răspunsului
Exemplu de 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({});
},
);Răspuns cu instrumente de funcții
Atașați instrumente astfel încât AI-ul să vă poată apela API-urile în timpul conversației:
{
"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"
}
}
}
]
}Fișă rapidă a nivelurilor de produs
| Produs | Latență | Raționament | Confirmare |
|---|---|---|---|
spark | Cea mai mică | De bază | — |
bolt | Mică | Îmbunătățit | — |
storm-base | Medie | Puternic | — |
storm-base-with-ack | Medie | Puternic | Text de umplere automat în timpul procesării |
storm-extra | Mai mare | Profund | — |
storm-extra-with-ack | Mai mare | Profund | Text de umplere automat în timpul procesării |
Asociate
Evenimentul neblocant de finalizare a apelului.
Schema JSON completă pentru tools[] și contractul endpointului semnat.
Abonați mai multe URL-uri la telephony.incoming / web.incoming.
Modele pentru prompturi, instrumente și teste A/B pentru fiecare apelant.