telephony.incoming / web.incoming
Webhook blocant care configurează în timp real o apelare de intrare.
Când un apel telefonic de intrare ajunge la un număr fără un
agent atribuit sau când începe o sesiune de widget web cu o cheie publicabilă în
mode="webhook", ThunderPhone trimite o solicitare blocantă
telephony.incoming / web.incoming către
URL-ul webhook tradițional
și așteaptă până la 10 secunde pentru un răspuns de configurare. 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 fluxul complet.
Schimbul blocant nu are soluție de rezervă: dacă handlerul dumneavoastră returnează un
status non-2xx, expiră sau returnează o configurație care nu trece validarea,
apelul este respins (apelul telefonic nu se conectează; solicitarea sesiunii
widgetului eșuează cu 502/422). Răspundeți rapid — apelantul aude tonul de apel
în timp ce luați decizia.
Î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-ul apelului |
origin_domain | string | Originea paginii care găzduiește widgetul |
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 |
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 | string | da | Prompt de sistem care controlează agentul |
voice | string | da | ID vocal din GET /v1/voices, de exemplu john. voice_name este acceptat ca alias. Vocile necunoscute nu trec validarea și resping apelul |
product | string | nu | Valoarea implicită este spark. Valori permise: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | string | nu | minimal, base (implicit) sau extra. Suprascris pentru produsele Storm: storm-extra* impune extra, iar celelalte storm-* impun base |
audio_context_mode | string | 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 replici din audio-ul apelantului, nu doar cea mai recentă replică, pentru a îmbunătăți corectările și colectarea datelor cu multe litere sau numere, cu un mic cost suplimentar de latență și cost. Este activat implicit pentru sesiunile de intrare și dezactivat pentru apelurile telefonice de ieșire; null păstrează valoarea implicită |
storm_feedback_mode | string | nu | none, acknowledgement (implicit) sau tick |
language | string | nu | Prescurtare pentru primary_language |
primary_language | string | 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 | array of string | nu | Limbi suplimentare la care agentul poate comuta |
native_voice_switching | boolean | nu | Valoarea implicită este false. Când apelul comută la altă limbă, folosiți o voce nativă pentru acea limbă (potrivită după gen) în loc să păstrați vocea configurată |
background_track | string | null | nu | ID audio ambiental sau null |
acknowledgement_prompt_mode | string | nu | auto (implicit) sau manual (produse Storm-with-ack) |
acknowledgement_prompt | string | nu | Folosit când acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | nu | 5–120. Secunde de tăcere ale apelantului înainte de o verificare |
silence_max_checkins | integer | null | nu | 1–10 |
silence_checkins_enabled | boolean | nu | Valoarea implicită este true |
connect_tone_enabled | boolean | nu | Valoarea implicită este false |
voicemail_action | string | nu | prompt (implicit), hangup sau message |
voicemail_message | string | nu | Folosit când voicemail_action="message" |
agent_name | string | nu | Nume afișat raportat în tablourile de bord și widget |
org_name | string | nu | Numele de afișare al organizației pentru persona agentului |
tools | array | nu | Scheme inline pentru instrumente de funcții (consultați Instrumente de funcții) |
call_id | integer | nu | Ecou opțional al ID-ului apelului din cerere; este ignorat |
Deoarece prompt și voice sunt obligatorii, returnarea {} sau a oricărui
răspuns care nu trece validarea respinge apelul cu 422 — nu există
o rezervă pentru agent static pe această rută (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.