ThunderPhone 2.0 jest już dostępny.Uruchom samodzielnie, od 2¢/min.Przeczytaj komunikat

Webhooks

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, 408 albo 429, 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-Signature oraz ten sam nagłówek X-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"
  }
}
PoleTypOpis
call_idintegerIdentyfikator połączenia — stały we wszystkich zdarzeniach dla tego połączenia
from_numberstringNumer rozmówcy w formacie E.164
to_numberstringMiejsce 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"
  }
}
PoleTypOpis
call_idintegerIdentyfikator połączenia
origin_domainstringŹ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_prefixstringPierwsze znaki klucza publicznego, który otworzył sesję
language, primary_languagestringObecne, gdy sesja widżetu zażądała zastąpienia języka
voicestringObecne, gdy sesja widżetu zażądała zastąpienia głosu
website_contextstringObecne, 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":   []
}
PoleTypWymaganeOpis
promptciąg znakówtakPrompt systemowy sterujący agentem
voiceciąg znakówtakIdentyfikator 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
productciąg znakównieDomyślnie spark. Dozwolone: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelciąg znakównieminimal, base (domyślnie) lub extra. Zastępowane dla produktów Storm: storm-extra* wymusza extra, pozostałe storm-* wymuszają base
audio_context_modeciąg znakówniefull (domyślnie) lub reduced
watchdog_enabledwartość logicznanieWłącz nadzór dla tego połączenia. Domyślnie false
additional_audio_contextwartość logiczna | nullnieUwzglę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_modeciąg znakównienone, acknowledgement (domyślnie) lub tick
languageciąg znakównieSkrót dla primary_language
primary_languageciąg znakównieKod języka, znormalizowany (domyślnie en). Kody, których nie można rozpoznać, powodują odrzucenie połączenia
has_additional_languageswartość logicznanieDomyślnie false
additional_languagestablica ciągów znakównieDodatkowe języki, na które agent może się przełączyć
native_voice_switchingwartość logicznanieDomyś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_switchingciąg znaków | nullnieask_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_trackciąg znaków | nullnieIdentyfikator dźwięku tła lub null
acknowledgement_prompt_modeciąg znakównieauto (domyślnie) lub manual (produkty Storm-with-ack)
acknowledgement_promptciąg znakównieUżywane, gdy acknowledgement_prompt_mode="manual"
silence_interval_secondsliczba całkowita | nullnie5–120. Liczba sekund ciszy rozmówcy przed sprawdzeniem
silence_max_checkinsliczba całkowita | nullnie1–10
silence_checkins_enabledwartość logicznanieDomyślnie true
connect_tone_enabledwartość logicznanieDomyślnie false
pronunciation_dictswartość logiczna | nullnieDomyś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_actionciąg znakównieprompt (domyślnie), hangup lub message
voicemail_messageciąg znakównieUżywane, gdy voicemail_action="message"
agent_nameciąg znakównieNazwa wyświetlana raportowana w panelach i widżecie
org_nameciąg znakównieNazwa wyświetlana organizacji dla persony agenta
toolstablicanieWbudowane schematy narzędzi funkcji (zobacz Narzędzia funkcji)
transfer_destinationstablica obiektów | nullnieDo 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
timezoneciąg znakównieStrefa 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_idliczba całkowitanieOpcjonalne 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

Python (FastAPI)
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 {}
Node.js (Express)
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

ProduktOpóźnienieRozumowaniePotwierdzenie
sparkNajniższePodstawowe—
boltNiskieUlepszone—
storm-baseŚrednieZaawansowane—
storm-base-with-ackŚrednieZaawansowaneAutomatyczna fraza wypełniająca podczas przetwarzania
storm-extraWyższeGłębokie—
storm-extra-with-ackWyższeGłębokieAutomatyczna fraza wypełniająca podczas przetwarzania

Powiązane