Open in
telephony.incoming / web.incoming
Blokující webhook, který v reálném čase upravuje konfiguraci příchozího hovoru.
Když příchozí telefonní hovor dorazí na číslo bez přiřazeného
agenta nebo když se relace webového widgetu spustí na publikovatelném klíči v režimu
mode="webhook", ThunderPhone odešle blokující
požadavek telephony.incoming / web.incoming na vaši
starší URL webhooku
a čeká na odpověď s konfigurací: až 13 sekund pro
telefonní hovor včetně jednoho opakování a až 10 sekund pro relaci
widgetu (viz Časové limity a opakování). Tuto
výměnu použijte k dynamickému výběru promptu, hlasu a nástrojů pro každý hovor —
kompletní postup najdete v průvodci dynamickou konfigurací hovorů.
Časové limity a opakované pokusy
Odpovězte rychle: volající čeká na lince, zatímco se rozhodujete.
Pro telefonní hovor (telephony.incoming):
- První pokus může trvat až 10 sekund.
- Pokud vyprší časový limit, nelze navázat spojení nebo je vrácena odpověď
5xx,408či429, ThunderPhone provede ještě jeden pokus. Opakovaný pokus získá zbývající část 13sekundového limitu pro celou výměnu: přibližně 3 sekundy po prvním pokusu, u kterého vypršel časový limit, a téměř všech 13 sekund po rychlém selhání. Pokud by zbývaly méně než 2 sekundy, opakovaný pokus se neprovede. - Jakákoli jiná odpověď
4xx, odpověď přesahující limit velikosti nebo odpověď2xx, jejíž tělo neobsahuje platnou konfiguraci, je konečná a neopakuje se. - Oba pokusy odesílají stejné tělo, stejnou hlavičku
X-ThunderPhone-Signaturea stejnou hlavičkuX-ThunderPhone-Idempotency-Key(telephony-incoming-<call_id>), takže obslužná rutina s vedlejšími účinky může opakovaný pokus rozpoznat.
Záložní agent není k dispozici. Pokud nedorazí použitelná konfigurace, hovor se ihned ukončí, místo aby volající čekal na tiché lince. V historii hovorů se zobrazí jako neúspěšný nezpoplatněný hovor s hodnotou end_reason inbound_config_webhook_failed a odešle se běžná událost telephony.complete.
Relace widgetu (web.incoming) má jeden pokus v délce až 10 sekund. Pokud selže, požadavek relace selže s kódem 502 nebo 422.
Datová část požadavku
Pro telefonní hovory (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Pole | Typ | Popis |
|---|---|---|
call_id | celé číslo | ID hovoru — stabilní napříč všemi událostmi tohoto hovoru |
from_number | řetězec | Číslo volajícího ve formátu E.164 |
to_number | řetězec | Cíl ve formátu E.164 (jedno z vašich čísel ThunderPhone) |
Pro relace webového widgetu (web.incoming) data identifikuje
stránku s vloženým widgetem namísto telefonních čísel:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Pole | Typ | Popis |
|---|---|---|
call_id | integer | ID hovoru |
origin_domain | string | Původ stránky hostující widget, jak jej odeslal prohlížeč. Klient bez prohlížeče může odeslat libovolnou hodnotu, proto jej nepovažujte za důkaz o tom, odkud požadavek přišel |
publishable_key_prefix | string | První znaky publikovatelného klíče, který otevřel relaci |
language, primary_language | string | Přítomno, když relace widgetu požadovala přepsání jazyka |
voice | string | Přítomno, když relace widgetu požadovala přepsání hlasu |
website_context | string | Přítomno, když widget předal kontext stránky pro danou relaci |
Pole přepsání (language, voice, website_context a jakékoli
variables prohlížeče) dorazí pouze tehdy, když je publikovatelný klíč povoluje ve svých
přepsáních prohlížeče. Požadavek relace obsahující přepsání, které klíč nepovoluje, je odmítnut ještě před zavoláním tohoto webhooku.
Schéma odpovědi
Vraťte objekt JSON popisující konfiguraci agenta pro tento hovor.
prompt a voice jsou povinné; vše ostatní je volitelné.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Pole | Typ | Povinné | Popis |
|---|---|---|---|
prompt | řetězec | ano | Systémový prompt řídící agenta |
voice | řetězec | ano | ID hlasu z GET /v1/voices, např. john. voice_name je přijímáno jako alias. Neznámé hlasy neprojdou ověřením a hovor se odmítne |
product | řetězec | ne | Výchozí hodnota je spark. Povolené hodnoty: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | řetězec | ne | minimal, base (výchozí) nebo extra. Pro produkty Storm je přepsáno: storm-extra* vynutí extra, ostatní storm-* vynutí base |
audio_context_mode | řetězec | ne | full (výchozí) nebo reduced |
watchdog_enabled | boolean | ne | Povolí dohled pro tento hovor. Výchozí hodnota je false |
additional_audio_context | boolean | null | ne | Zahrne posledních několik tahů zvuku volajícího namísto pouze nejnovějšího tahu, což zlepšuje opravy a sběr údajů s častým hláskováním nebo čísly za cenu mírně vyšší latence a nákladů. Pro příchozí relace je ve výchozím nastavení zapnuto a pro odchozí telefonní hovory vypnuto; null zachová výchozí nastavení |
storm_feedback_mode | řetězec | ne | none, acknowledgement (výchozí) nebo tick |
language | řetězec | ne | Zkratka pro primary_language |
primary_language | řetězec | ne | Kód jazyka, normalizovaný (výchozí hodnota en). Nevyřešitelné kódy hovor odmítnou |
has_additional_languages | boolean | ne | Výchozí hodnota je false |
additional_languages | pole řetězců | ne | Další jazyky, do kterých může agent přepnout |
native_voice_switching | boolean | ne | Výchozí hodnota je false. Když hovor přepne do jiného jazyka, nahraďte nastavený hlas hlasem, který je pro daný jazyk rodilý (se stejným pohlavím) |
language_switching | řetězec | null | ne | ask_first (výchozí) nebo follow_caller; null je stejné jako ask_first. Určuje, jak vícejazyčný agent reaguje, když druhá osoba mluví jiným z jeho jazyků. ask_first přepne ihned, když o jazyk požádá nebo v něm řekne celou větu, a po něčem krátkém, například „ano“, se zeptá, zda v něm pokračovat. follow_caller přepne do jakéhokoli nastaveného jazyka, kterým osoba mluví, i kdyby šlo jen o jedno slovo. Platí pouze, když je nastaveno additional_languages |
background_track | řetězec | null | ne | ID zvuku na pozadí nebo null |
acknowledgement_prompt_mode | řetězec | ne | auto (výchozí) nebo manual (produkty Storm s potvrzením) |
acknowledgement_prompt | řetězec | ne | Použije se, když acknowledgement_prompt_mode="manual" |
silence_interval_seconds | integer | null | ne | 5–120. Počet sekund ticha volajícího před kontrolním oslovením |
silence_max_checkins | integer | null | ne | 1–10 |
silence_checkins_enabled | boolean | ne | Výchozí hodnota je true |
connect_tone_enabled | boolean | ne | Výchozí hodnota je false |
pronunciation_dicts | boolean | null | ne | Výchozí hodnota je false; null je stejné jako false. Když je nastaveno true, syntéza řeči Cartesia pro hovor používá slovník výslovnosti ThunderPhone pro jazyk hovoru, pokud existuje. Funguje pro telefonní hovory i hovory z widgetu. Platí pouze pro vlastní hlasy Cartesia od ThunderPhone, nikoli pro hlasy na vašem vlastním klíči API Cartesia. Nepoužívá se s agent_id, kde se použije nastavení uloženého agenta |
voicemail_action | řetězec | ne | prompt (výchozí), hangup nebo message |
voicemail_message | řetězec | ne | Použije se, když voicemail_action="message" |
agent_name | řetězec | ne | Zobrazovaný název uváděný v dashboardech a widgetu |
org_name | řetězec | ne | Zobrazovaný název organizace pro personu agenta |
tools | pole | ne | Vložená schémata funkčních nástrojů (viz Funkční nástroje) |
transfer_destinations | pole objektů | null | ne | Až 50 položek {name, number}, do kterých může tento hovor přepojit, ověřovaných stejně jako čísla pro přepojení agenta. Agent přepojuje podle názvu a pouze na tato čísla; [] znamená žádná přepojení. Vynechte tuto hodnotu nebo odešlete null, aby agent mohl přepojit na číslo, které zvolí |
timezone | řetězec | ne | Časové pásmo IANA pro tento hovor, například America/Chicago. Agent v něm uvádí datum a čas před vlastním časovým pásmem agenta. Je také povoleno vedle agent_id. Hodnoty, které nejsou pásmy IANA, se ignorují. Viz Nastavení časového pásma pro jednotlivý hovor |
call_id | integer | ne | Volitelné zopakování ID hovoru z požadavku; ignoruje se |
Protože jsou prompt a voice povinné, vrácení {} nebo jakékoli
odpovědi, která neprojde ověřením, hovor odmítne: telefonní hovor se ukončí
podle popisu v části Časové limity a opakované pokusy a
požadavek na relaci widgetu selže s 422. Na této cestě není k dispozici
záložní statický agent (číslo nebo klíč v režimu webhooku nemá přiřazeného
agenta).
Limit velikosti odpovědi
Příklad obslužné funkce
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({});
},
);Odpověď s funkčními nástroji
Připojte nástroje, aby AI mohla během konverzace volat vaše API:
{
"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"
}
}
}
]
}Přehled produktových úrovní
| Produkt | Latence | Uvažování | Potvrzení |
|---|---|---|---|
spark | Nejnižší | Základní | — |
bolt | Nízká | Vylepšené | — |
storm-base | Střední | Silné | — |
storm-base-with-ack | Střední | Silné | Automatická výplň během uvažování |
storm-extra | Vyšší | Hloubkové | — |
storm-extra-with-ack | Vyšší | Hloubkové | Automatická výplň během uvažování |
Související
Neblokující událost ukončení hovoru.
Úplné schéma JSON pro tools[] a kontrakt podepsaného endpointu.
Přihlaste k odběru více adres URL pro telephony.incoming / web.incoming.
Vzory pro prompty, nástroje a A/B testy pro jednotlivé volající.