Open in
telephony.incoming / web.incoming
Blokujący webhook, który kształtuje konfigurację połączenia przychodzącego w czasie rzeczywistym.
Gdy przychodzące połączenie telefoniczne trafia na numer bez przypisanego
agenta głosowego albo sesja widżetu internetowego rozpoczyna się przy użyciu klucza publikowalnego w
mode="webhook", ThunderPhone wysyła blokujące
żądanie telephony.incoming / web.incoming na Twój
starszy adres URL webhooka
i czeka na odpowiedź z konfiguracją: do 13 sekund w przypadku
połączenia telefonicznego, z uwzględnieniem jednej ponownej próby, oraz do 10 sekund w przypadku sesji widżetu
(zobacz Limity czasu i ponowne próby). Użyj tej
wymiany, aby dynamicznie wybierać prompt, głos i narzędzia dla każdego połączenia —
pełny wzorzec znajdziesz w przewodniku po dynamicznej konfiguracji połączeń.
Przekroczenia limitu czasu i ponowne próby
Odpowiadaj szybko: rozmówca czeka na linii, gdy podejmujesz decyzję.
W przypadku połączenia telefonicznego (telephony.incoming):
- Pierwsza próba może trwać do 10 sekund.
- Jeśli przekroczy limit czasu, nie może się połączyć lub otrzyma odpowiedź
5xx,408albo429, ThunderPhone podejmuje jeszcze jedną próbę. Ponowna próba otrzymuje pozostałą część 13-sekundowego budżetu na całą wymianę: około 3 sekundy po pierwszej próbie, która przekroczyła limit czasu, i prawie całe 13 sekund po szybkim niepowodzeniu. Nie podejmuje się ponownej próby, gdy pozostałyby mniej niż 2 sekundy. - Każda inna odpowiedź
4xx, odpowiedź przekraczająca limit rozmiaru lub odpowiedź2xx, której treść nie jest prawidłową konfiguracją, jest ostateczna i nie jest ponawiana. - Obie próby wysyłają tę samą treść, ten sam nagłówek
X-ThunderPhone-Signatureoraz ten sam nagłówekX-ThunderPhone-Idempotency-Key(telephony-incoming-<call_id>), dzięki czemu procedura obsługi powodująca skutki uboczne może rozpoznać ponowną próbę.
Nie ma agenta awaryjnego. Jeśli nie nadejdzie użyteczna konfiguracja,
połączenie jest natychmiast kończone, zamiast pozostawiać rozmówcę na cichej
linii. W Historii połączeń jest widoczne jako nieudane połączenie, za które nie naliczono opłaty, z
end_reason inbound_config_webhook_failed, a standardowe zdarzenie
telephony.complete jest wysyłane.
Sesja widżetu (web.incoming) otrzymuje jedną próbę trwającą do 10
sekund. Jeśli się nie powiedzie, żądanie sesji kończy się błędem 502 lub 422.
Ładunek żądania
Dla połączeń telefonicznych (telephony.incoming):
{
"type": "telephony.incoming",
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
}
}| Pole | Typ | Opis |
|---|---|---|
call_id | integer | Identyfikator połączenia — stały we wszystkich zdarzeniach dla tego połączenia |
from_number | string | Numer rozmówcy w formacie E.164 |
to_number | string | Miejsce docelowe E.164 (jeden z Twoich numerów ThunderPhone) |
W przypadku sesji widżetu internetowego (web.incoming) pole data identyfikuje
stronę osadzającą zamiast numerów telefonów:
{
"type": "web.incoming",
"data": {
"call_id": 987654322,
"origin_domain": "https://example.com",
"publishable_key_prefix": "pk_live_a1b2"
}
}| Pole | Typ | Opis |
|---|---|---|
call_id | integer | Identyfikator połączenia |
origin_domain | string | Źródło strony hostującej widżet, przesłane przez przeglądarkę. Klient inny niż przeglądarka może wysłać dowolną wartość, dlatego nie traktuj go jako dowodu miejsca, z którego pochodzi żądanie |
publishable_key_prefix | string | Pierwsze znaki klucza publicznego, który otworzył sesję |
language, primary_language | string | Obecne, gdy sesja widżetu zażądała zastąpienia języka |
voice | string | Obecne, gdy sesja widżetu zażądała zastąpienia głosu |
website_context | string | Obecne, gdy widżet przekazał kontekst strony dla sesji |
Pola zastępujące (language, voice, website_context oraz wszelkie
przeglądarkowe variables) są przekazywane tylko wtedy, gdy klucz publiczny zezwala na nie w swoich zastąpieniach przeglądarkowych. Żądanie sesji zawierające pole, na które klucz nie zezwala, jest odrzucane przed wywołaniem tego webhooka.
Schemat odpowiedzi
Zwróć obiekt JSON opisujący konfigurację agenta dla tego połączenia.
prompt i voice są wymagane; wszystkie pozostałe pola są opcjonalne.
{
"prompt": "You are a helpful booking assistant for Acme Restaurant.",
"voice": "john",
"product": "spark",
"background_track": null,
"tools": []
}| Pole | Typ | Wymagane | Opis |
|---|---|---|---|
prompt | ciąg znaków | tak | Prompt systemowy sterujący agentem |
voice | ciąg znaków | tak | Identyfikator głosu z GET /v1/voices, np. john. voice_name jest akceptowane jako alias. Nieznane głosy nie przechodzą walidacji i powodują odrzucenie połączenia |
product | ciąg znaków | nie | Domyślnie spark. Dozwolone: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack |
thinking_level | ciąg znaków | nie | minimal, base (domyślnie) lub extra. Zastępowane dla produktów Storm: storm-extra* wymusza extra, pozostałe storm-* wymuszają base |
audio_context_mode | ciąg znaków | nie | full (domyślnie) lub reduced |
watchdog_enabled | wartość logiczna | nie | Włącz nadzór dla tego połączenia. Domyślnie false |
additional_audio_context | wartość logiczna | null | nie | Uwzględnia kilka ostatnich tur dźwięku rozmówcy zamiast tylko najnowszej tury, co poprawia korekty oraz zbieranie danych zawierających pisownię i liczby, kosztem niewielkiego wzrostu opóźnienia i kosztu. Domyślnie włączone dla sesji przychodzących i wyłączone dla wychodzących połączeń telefonicznych; null zachowuje ustawienie domyślne |
storm_feedback_mode | ciąg znaków | nie | none, acknowledgement (domyślnie) lub tick |
language | ciąg znaków | nie | Skrót dla primary_language |
primary_language | ciąg znaków | nie | Kod języka, znormalizowany (domyślnie en). Kody, których nie można rozpoznać, powodują odrzucenie połączenia |
has_additional_languages | wartość logiczna | nie | Domyślnie false |
additional_languages | tablica ciągów znaków | nie | Dodatkowe języki, na które agent może się przełączyć |
native_voice_switching | wartość logiczna | nie | Domyślnie false. Gdy połączenie przełącza się na inny język, zamień głos na rodzimy dla tego języka (dopasowany według płci), zamiast zachowywać skonfigurowany głos |
language_switching | ciąg znaków | null | nie | ask_first (domyślnie) lub follow_caller; null działa tak samo jak ask_first. Określa, jak wielojęzyczny agent obsługuje sytuację, gdy rozmówca mówi w innym z jego języków. ask_first przełącza się od razu, gdy rozmówca poprosi o język lub wypowie w nim pełne zdanie, a po czymś krótkim, takim jak „tak”, pyta, czy kontynuować w tym języku. follow_caller przełącza się na dowolny skonfigurowany język, którym mówi rozmówca, nawet po jednym słowie. Dotyczy tylko sytuacji, gdy ustawiono additional_languages |
background_track | ciąg znaków | null | nie | Identyfikator dźwięku tła lub null |
acknowledgement_prompt_mode | ciąg znaków | nie | auto (domyślnie) lub manual (produkty Storm-with-ack) |
acknowledgement_prompt | ciąg znaków | nie | Używane, gdy acknowledgement_prompt_mode="manual" |
silence_interval_seconds | liczba całkowita | null | nie | 5–120. Liczba sekund ciszy rozmówcy przed sprawdzeniem |
silence_max_checkins | liczba całkowita | null | nie | 1–10 |
silence_checkins_enabled | wartość logiczna | nie | Domyślnie true |
connect_tone_enabled | wartość logiczna | nie | Domyślnie false |
pronunciation_dicts | wartość logiczna | null | nie | Domyślnie false; null działa tak samo jak false. Gdy ustawiono true, mowa Cartesia w połączeniu używa słownika wymowy ThunderPhone dla języka połączenia, jeśli taki istnieje. Działa w połączeniach telefonicznych i widżetach. Dotyczy tylko własnych głosów Cartesia ThunderPhone, a nie głosów używanych z własnym kluczem API Cartesia. Nie jest używane z agent_id, gdzie obowiązują ustawienia zapisanego agenta |
voicemail_action | ciąg znaków | nie | prompt (domyślnie), hangup lub message |
voicemail_message | ciąg znaków | nie | Używane, gdy voicemail_action="message" |
agent_name | ciąg znaków | nie | Nazwa wyświetlana raportowana w panelach i widżecie |
org_name | ciąg znaków | nie | Nazwa wyświetlana organizacji dla persony agenta |
tools | tablica | nie | Wbudowane schematy narzędzi funkcji (zobacz Narzędzia funkcji) |
transfer_destinations | tablica obiektów | null | nie | Do 50 wpisów {name, number}, do których to połączenie może zostać przekazane, walidowanych tak jak numery przekierowań agenta. Agent przekazuje połączenia według nazwy i tylko do tych wpisów; [] oznacza brak przekierowań. Pomiń to pole lub wyślij null, aby pozwolić agentowi przekazać połączenie na wybrany przez niego numer |
timezone | ciąg znaków | nie | Strefa czasowa IANA dla tego połączenia, np. America/Chicago. Agent podaje w niej datę i godzinę, z pierwszeństwem przed własną strefą czasową. Dozwolone także obok agent_id. Wartości, które nie są strefami IANA, są ignorowane. Zobacz Ustaw strefę czasową dla każdego połączenia |
call_id | liczba całkowita | nie | Opcjonalne powtórzenie identyfikatora połączenia z żądania; ignorowane |
Ponieważ prompt i voice są wymagane, zwrócenie {} lub dowolnej
odpowiedzi, która nie przejdzie walidacji, odrzuca połączenie: połączenie telefoniczne jest kończone
zgodnie z opisem w Limitach czasu i ponowieniach, a żądanie sesji
widżetu kończy się błędem 422. Na tej ścieżce nie ma awaryjnego użycia statycznego agenta
(numer lub klucz w trybie webhook nie ma przypisanego agenta).
Limit rozmiaru odpowiedzi
Przykładowy moduł obsługi
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({});
},
);Odpowiedź z narzędziami funkcyjnymi
Dołącz narzędzia, aby AI mogła wywoływać Twoje API w trakcie rozmowy:
{
"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"
}
}
}
]
}Ściągawka dotycząca poziomów produktów
| Produkt | Opóźnienie | Rozumowanie | Potwierdzenie |
|---|---|---|---|
spark | Najniższe | Podstawowe | — |
bolt | Niskie | Ulepszone | — |
storm-base | Średnie | Zaawansowane | — |
storm-base-with-ack | Średnie | Zaawansowane | Automatyczna fraza wypełniająca podczas przetwarzania |
storm-extra | Wyższe | Głębokie | — |
storm-extra-with-ack | Wyższe | Głębokie | Automatyczna fraza wypełniająca podczas przetwarzania |
Powiązane
Nieblokujące zdarzenie zakończenia połączenia.
Pełny schemat JSON dla tools[] i podpisany kontrakt endpointu.
Subskrybuj wiele adresów URL dla telephony.incoming / web.incoming.
Wzorce promptów, narzędzi i testów A/B dla poszczególnych rozmówców.