ThunderPhone 2.0 je tady.Spustíte sami, od 2 ¢/min.Přečíst oznámení

Webhooks

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 či 429, 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-Signature a stejnou hlavičku X-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"
  }
}
PoleTypPopis
call_idcelé čísloID 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ězecCí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"
  }
}
PoleTypPopis
call_idintegerID hovoru
origin_domainstringPů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_prefixstringPrvní znaky publikovatelného klíče, který otevřel relaci
language, primary_languagestringPřítomno, když relace widgetu požadovala přepsání jazyka
voicestringPřítomno, když relace widgetu požadovala přepsání hlasu
website_contextstringPří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":   []
}
PoleTypPovinnéPopis
promptřetězecanoSystémový prompt řídící agenta
voiceřetězecanoID 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ězecneVýchozí hodnota je spark. Povolené hodnoty: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelřetězecneminimal, base (výchozí) nebo extra. Pro produkty Storm je přepsáno: storm-extra* vynutí extra, ostatní storm-* vynutí base
audio_context_modeřetězecnefull (výchozí) nebo reduced
watchdog_enabledbooleannePovolí dohled pro tento hovor. Výchozí hodnota je false
additional_audio_contextboolean | nullneZahrne 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ězecnenone, acknowledgement (výchozí) nebo tick
languageřetězecneZkratka pro primary_language
primary_languageřetězecneKód jazyka, normalizovaný (výchozí hodnota en). Nevyřešitelné kódy hovor odmítnou
has_additional_languagesbooleanneVýchozí hodnota je false
additional_languagespole řetězcůneDalší jazyky, do kterých může agent přepnout
native_voice_switchingbooleanneVý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 | nullneask_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 | nullneID zvuku na pozadí nebo null
acknowledgement_prompt_modeřetězecneauto (výchozí) nebo manual (produkty Storm s potvrzením)
acknowledgement_promptřetězecnePoužije se, když acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullne5–120. Počet sekund ticha volajícího před kontrolním oslovením
silence_max_checkinsinteger | nullne1–10
silence_checkins_enabledbooleanneVýchozí hodnota je true
connect_tone_enabledbooleanneVýchozí hodnota je false
pronunciation_dictsboolean | nullneVý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ězecneprompt (výchozí), hangup nebo message
voicemail_messageřetězecnePoužije se, když voicemail_action="message"
agent_nameřetězecneZobrazovaný název uváděný v dashboardech a widgetu
org_nameřetězecneZobrazovaný název organizace pro personu agenta
toolspoleneVložená schémata funkčních nástrojů (viz Funkční nástroje)
transfer_destinationspole objektů | nullneAž 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ězecneČ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_idintegerneVolitelné 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

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({});
  },
);

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í

ProduktLatenceUvažováníPotvrzení
sparkNejnižšíZákladní—
boltNízkáVylepšené—
storm-baseStředníSilné—
storm-base-with-ackStředníSilnéAutomatická výplň během uvažování
storm-extraVyššíHloubkové—
storm-extra-with-ackVyššíHloubkovéAutomatická výplň během uvažování

Související