ThunderPhone 2.0 este acum disponibil.Self-service, de la 2 ¢/min.Citiți anunțul

Webhooks

telephony.incoming / web.incoming

Webhook blocant care configurează în timp real o apelare de intrare.

Când un apel telefonic primit ajunge la un număr fără un agent atribuit sau când o sesiune de widget web începe pe o cheie publicabilă în mode="webhook", ThunderPhone trimite o solicitare blocantă telephony.incoming / web.incoming către adresa URL webhook veche și așteaptă un răspuns de configurare: până la 13 secunde pentru un apel telefonic, inclusiv o reîncercare, și până la 10 secunde pentru o sesiune de widget (consultați Expirări și reîncercări). 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 modelul complet.

Expirări de timp și reîncercări

Răspundeți rapid: apelantul așteaptă pe linie în timp ce decideți.

Pentru un apel telefonic (telephony.incoming):

  • Prima încercare poate dura până la 10 secunde.
  • Dacă expiră, nu se poate conecta sau primește un răspuns 5xx, 408 ori 429, ThunderPhone încearcă încă o dată. Reîncercarea primește timpul rămas dintr-un buget de 13 secunde pentru întregul schimb: aproximativ 3 secunde după o primă încercare care a expirat, aproape toate cele 13 secunde după un eșec rapid. Nu există reîncercare dacă ar rămâne mai puțin de 2 secunde.
  • Orice alt răspuns 4xx, un răspuns care depășește limita de dimensiune sau un răspuns 2xx al cărui corp nu este o configurație validă este final și nu este reîncercat.
  • Ambele încercări trimit același corp, același antet X-ThunderPhone-Signature și același antet X-ThunderPhone-Idempotency-Key (telephony-incoming-<call_id>), astfel încât un handler cu efecte secundare poate recunoaște reîncercarea.

Nu există agent de rezervă. Dacă nu sosește o configurație utilizabilă, apelul este încheiat imediat, în loc ca apelantul să rămână pe o linie silențioasă. Acesta apare în istoricul apelurilor ca un apel eșuat, nefacturat, cu end_reason inbound_config_webhook_failed, iar evenimentul obișnuit telephony.complete este trimis.

O sesiune de widget (web.incoming) primește o singură încercare de până la 10 secunde. Dacă aceasta eșuează, solicitarea sesiunii eșuează cu 502 sau 422.

Încărcătura solicitării

Pentru apelurile telefonice (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
CâmpTipDescriere
call_idintegerID-ul apelului — stabil în toate evenimentele pentru acest apel
from_numberstringNumărul E.164 al apelantului
to_numberstringDestinaț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âmpTipDescriere
call_idintegerID apel
origin_domainstringOriginea paginii care găzduiește widgetul, așa cum a transmis-o browserul. Un client care nu este browser poate trimite orice valoare, așadar nu o tratați drept dovadă a originii solicitării
publishable_key_prefixstringPrimele caractere ale cheii publicabile care a deschis sesiunea
language, primary_languagestringPrezente când sesiunea widgetului a solicitat o suprascriere a limbii
voicestringPrezent când sesiunea widgetului a solicitat o suprascriere a vocii
website_contextstringPrezent când widgetul a transmis contextul paginii pentru fiecare sesiune

Câmpurile de suprascriere (language, voice, website_context și orice variables din browser) sosesc numai când cheia publicabilă le permite în suprascrierile din browser. O solicitare de sesiune care conține una pe care cheia nu o permite este respinsă înainte ca acest webhook să fie apelat.


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âmpTipObligatoriuDescriere
promptșirdaPromptul de sistem care ghidează agentul
voiceșirdaID-ul vocii din GET /v1/voices, de exemplu john. voice_name este acceptat ca alias. Vocile necunoscute nu trec validarea și resping apelul
productșirnuValoarea implicită este spark. Permise: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelșirnuminimal, base (implicit) sau extra. Suprascris pentru produsele Storm: storm-extra* impune extra, iar alte storm-* impun base
audio_context_modeșirnufull (implicit) sau reduced
watchdog_enabledbooleannuActivați supravegherea pentru acest apel. Valoarea implicită este false
additional_audio_contextboolean | nullnuIncludeți ultimele câteva schimburi de audio ale apelantului, în loc doar de cel mai recent schimb, îmbunătățind corecturile și colectarea de date cu multe litere sau numere, cu un mic cost suplimentar de latență și preț. Este activat implicit pentru sesiunile de intrare și dezactivat pentru apelurile telefonice de ieșire; null păstrează valoarea implicită
storm_feedback_modeșirnunone, acknowledgement (implicit) sau tick. De asemenea, ce aude apelantul în timp ce modelele audio procesează un schimb atunci când audio_tier este hearing
languageșirnuFormă prescurtată pentru primary_language
primary_languageșirnuCod de limbă, normalizat (implicit en). Codurile care nu pot fi rezolvate resping apelul
has_additional_languagesbooleannuValoarea implicită este false
additional_languagesmatrice de șirurinuLimbi suplimentare la care agentul poate trece
native_voice_switchingbooleannuValoarea implicită este false. Când apelul trece la altă limbă, înlocuiți vocea configurată cu o voce nativă pentru limba respectivă (potrivită după gen)
language_switchingșir | nullnuask_first (implicit) sau follow_caller; null este echivalent cu ask_first. Modul în care un agent multilingv gestionează situația în care cealaltă persoană vorbește o altă limbă a sa. ask_first trece imediat când persoana cere limba respectivă sau spune o propoziție completă în ea și întreabă dacă să continue în acea limbă după ceva scurt, cum ar fi un „da”. follow_caller trece la orice limbă configurată pe care o vorbește persoana, chiar și pentru un singur cuvânt. Se aplică numai când este definit additional_languages
audio_tierșir | nullnuNumai pentru product: "bolt". standard (implicit; "" și null au același sens) rulează trei transcriitori (apelurile care procesează PHI folosesc numai transcriitori acoperiți de BAA). lean reduce la un singur transcriitor, care aude greșit mai des. hearing adaugă la standard un ansamblu puternic de modele audio care ascultă audio-ul apelantului în unele schimburi, care durează cu aproximativ 1–2 secunde mai mult. Alte produse ignoră această opțiune
endpointingșir | nullnufaster (200 ms), fast (300 ms), normal (500 ms, implicit) sau patient (800 ms); null este echivalent cu normal. Cât timp așteaptă un agent Bolt v2 după ce apelantul se oprește înainte de a cere modelului un răspuns. Așteptările mai scurte pot răspunde mai repede, dar trimit mai multe cereri pe care următoarele cuvinte ale apelantului le fac depășite, ceea ce crește costul modelului. Alte produse ignoră această opțiune
eagernessșir | nullnuDepreciat: denumirea lui endpointing până la 19 octombrie 2026, încă citită. Este ignorat când este definit endpointing
intelligenceșir | nullnuexpress, lite sau standard (implicit); null este echivalent cu standard. Nivelul de inteligență al unui agent bolt pe fluxul Bolt v2: ce model îi scrie răspunsurile. express are aproximativ 600 ms până la primul cuvânt, este mai ieftin decât Standard și urmează instrucțiunile cu cea mai mică fiabilitate; lite are aproximativ 900 ms, este cel mai ieftin și mai puțin fiabil; standard are aproximativ 900 ms și urmează cel mai bine instrucțiunile. Răspunsurile Express și Lite care ar apela doar un instrument sunt predate modelului Standard. Alte produse ignoră această opțiune
background_trackșir | nullnuID de audio ambiental sau null
acknowledgement_prompt_modeșirnuauto (implicit) sau manual (produse Storm-with-ack)
acknowledgement_promptșirnuUtilizat când acknowledgement_prompt_mode="manual"
silence_interval_secondsîntreg | nullnu5–120. Secunde de tăcere ale apelantului înainte de o verificare
silence_max_checkinsîntreg | nullnu1–10
silence_checkins_enabledbooleannuValoarea implicită este true
silence_final_actionșirnuDupă ultima verificare: hang_up (implicit) sau transfer către silence_transfer_destination
silence_transfer_destinationșirnuNumele unei intrări din transfer_destinations al acestei configurații; obligatoriu cu transfer. Fără transfer_destinations, transferul nu poate fi efectuat, iar agentul închide apelul
silence_final_messageșirnu≤ 300 de caractere, citit exact cum este scris; "" (implicit) folosește mesajul implicit
connect_tone_enabledbooleannuValoarea implicită este false
pronunciation_dictsboolean | nullnuValoarea implicită este false; null este echivalent cu false. Când este true, vocea Cartesia a apelului utilizează dicționarul de pronunție ThunderPhone pentru limba apelului, acolo unde există. Funcționează pentru apeluri telefonice și prin widget. Se aplică numai vocilor Cartesia proprii ThunderPhone, nu vocilor de pe propria cheie API Cartesia. Nu este utilizat cu agent_id, unde se aplică setările agentului salvat
voicemail_actionșirnuprompt (implicit), hangup sau message
voicemail_messageșirnuUtilizat când voicemail_action="message"
agent_nameșirnuNume afișat raportat în tablourile de bord și în widget
org_nameșirnuNumele afișat al organizației pentru persona agentului
toolsmatricenuScheme de instrumente de funcții inline (consultați Instrumente de funcții)
transfer_destinationsmatrice de obiecte | nullnuPână la 50 de intrări {name, number} către care acest apel poate fi transferat, validate ca numerele de transfer ale unui agent. Agentul transferă după nume și numai către acestea; [] înseamnă fără transferuri. Omiteți-l sau trimiteți null pentru a permite agentului să transfere către un număr ales de acesta
timezoneșirnuFus orar IANA pentru acest apel, cum ar fi America/Chicago. Agentul indică data și ora în acel fus, în locul propriului fus orar al agentului. Este permis și alături de agent_id. Valorile care nu sunt zone IANA sunt ignorate. Consultați Setarea fusului orar pentru fiecare apel
call_idîntregnuEcou opțional al ID-ului apelului din cerere; ignorat

Deoarece prompt și voice sunt obligatorii, returnarea {} sau a oricărui răspuns care nu trece validarea respinge apelul: un apel telefonic este încheiat conform descrierii din Expirări și reîncercări, iar o solicitare de sesiune pentru widget eșuează cu 422. Nu există o rezervă către un agent static pe această cale (un număr sau o cheie în modul webhook nu are un agent atribuit).


Limită de dimensiune a răspunsului


Exemplu de handler

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

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

ProdusLatențăRaționamentConfirmare
sparkCea mai micăDe bază—
boltMicăÎmbunătățit—
storm-baseMediePuternic—
storm-base-with-ackMediePuternicText de umplere automat în timpul procesării
storm-extraMai mareProfund—
storm-extra-with-ackMai mareProfundText de umplere automat în timpul procesării

Asociate