ThunderPhone 2.0 è arrivato.Attivazione self-service, da 2¢/min.Leggi l’annuncio

Webhooks

telephony.incoming / web.incoming

Webhook bloccante che definisce in tempo reale la configurazione di una chiamata in entrata.

Quando una chiamata telefonica in arrivo raggiunge un numero senza un agente assegnato, oppure una sessione di widget web viene avviata su una chiave pubblicabile in mode="webhook", ThunderPhone invia una richiesta bloccante telephony.incoming / web.incoming al tuo URL webhook legacy e attende una risposta di configurazione: fino a 13 secondi per una chiamata telefonica, incluso un tentativo aggiuntivo, e fino a 10 secondi per una sessione del widget (vedi Timeout e tentativi aggiuntivi). Usa questo scambio per scegliere dinamicamente un prompt, una voce e gli strumenti per ogni chiamata — consulta la guida alla configurazione dinamica delle chiamate per il modello end-to-end.

Timeout e nuovi tentativi

Rispondi rapidamente: il chiamante è in attesa in linea mentre decidi.

Per una chiamata telefonica (telephony.incoming):

  • Il primo tentativo può richiedere fino a 10 secondi.
  • Se va in timeout, non riesce a connettersi o riceve una risposta 5xx, 408 o 429, ThunderPhone prova un'altra volta. Il nuovo tentativo riceve il tempo rimanente di un budget di 13 secondi per l'intero scambio: circa 3 secondi dopo un primo tentativo andato in timeout, quasi tutti i 13 secondi dopo un errore rapido. Non viene effettuato alcun nuovo tentativo se rimarrebbero meno di 2 secondi.
  • Qualsiasi altra risposta 4xx, una risposta oltre il limite di dimensione o una 2xx il cui corpo non contiene una configurazione valida è definitiva e non viene ritentata.
  • Entrambi i tentativi inviano lo stesso corpo, lo stesso X-ThunderPhone-Signature e la stessa intestazione X-ThunderPhone-Idempotency-Key (telephony-incoming-<call_id>), così un gestore con effetti collaterali può riconoscere il nuovo tentativo.

Non è disponibile alcun agente di fallback. Se non arriva una configurazione utilizzabile, la chiamata viene terminata subito anziché lasciare il chiamante su una linea silenziosa. Nella cronologia chiamate appare come chiamata non fatturata non riuscita con end_reason inbound_config_webhook_failed e viene inviato il consueto evento telephony.complete.

Una sessione widget (web.incoming) riceve un solo tentativo fino a 10 secondi. Se non riesce, la richiesta della sessione non riesce con 502 o 422.

Payload della richiesta

Per le chiamate telefoniche (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
CampoTipoDescrizione
call_idintegerID della chiamata — stabile in tutti gli eventi di questa chiamata
from_numberstringNumero del chiamante in formato E.164
to_numberstringDestinazione E.164 (uno dei tuoi numeri ThunderPhone)

Per le sessioni del widget web (web.incoming), data identifica la pagina di incorporamento anziché i numeri telefonici:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
CampoTipoDescrizione
call_idintegerID chiamata
origin_domainstringOrigine della pagina che ospita il widget, come inviata dal browser. Un client non browser può inviare qualsiasi valore, quindi non trattarlo come prova della provenienza della richiesta
publishable_key_prefixstringPrimi caratteri della chiave pubblicabile che ha aperto la sessione
language, primary_languagestringPresente quando la sessione del widget ha richiesto un override della lingua
voicestringPresente quando la sessione del widget ha richiesto un override della voce
website_contextstringPresente quando il widget ha passato il contesto della pagina per sessione

I campi di override (language, voice, website_context e qualsiasi variables del browser) arrivano solo quando la chiave pubblicabile li consente nei suoi override del browser. Una richiesta di sessione che contiene un override non consentito dalla chiave viene rifiutata prima che questo webhook venga chiamato.


Schema della risposta

Restituisci un oggetto JSON che descriva la configurazione dell'agente per questa chiamata. prompt e voice sono obbligatori; tutto il resto è facoltativo.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
CampoTipoObbligatorioDescrizione
promptstringsìPrompt di sistema che guida l'agente
voicestringsìID della voce da GET /v1/voices, ad es. john. voice_name è accettato come alias. Le voci sconosciute non superano la convalida e rifiutano la chiamata
productstringnoPredefinito: spark. Valori consentiti: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringnominimal, base (predefinito) o extra. Sovrascritto per i prodotti Storm: storm-extra* impone extra, gli altri storm-* impongono base
audio_context_modestringnofull (predefinito) o reduced
watchdog_enabledbooleannoAbilita la supervisione per questa chiamata. Predefinito: false
additional_audio_contextboolean | nullnoInclude gli ultimi turni dell'audio del chiamante anziché solo il turno più recente, migliorando le correzioni e la raccolta di dati ricchi di ortografia/numeri, con un lieve impatto su latenza e costo. È attivo per impostazione predefinita nelle sessioni in entrata e disattivo nelle chiamate telefoniche in uscita; null mantiene il valore predefinito
storm_feedback_modestringnonone, acknowledgement (predefinito) o tick
languagestringnoAbbreviazione di primary_language
primary_languagestringnoCodice lingua, normalizzato (predefinito en). I codici non risolvibili rifiutano la chiamata
has_additional_languagesbooleannoPredefinito: false
additional_languagesarray di stringnoLingue aggiuntive a cui l'agente può passare
native_voice_switchingbooleannoPredefinito: false. Quando la chiamata passa a un'altra lingua, sostituisce la voce configurata con una voce madrelingua per quella lingua, abbinata per genere
language_switchingstring | nullnoask_first (predefinito) o follow_caller; null equivale a ask_first. Definisce come un agente multilingue gestisce l'altra persona che parla un'altra delle sue lingue. ask_first passa subito quando la persona richiede la lingua o pronuncia una frase completa in essa e chiede se continuare in quella lingua dopo qualcosa di breve, come un "sì". follow_caller passa a qualsiasi lingua configurata parlata dalla persona, anche per una sola parola. Si applica solo quando è impostato additional_languages
background_trackstring | nullnoID dell'audio ambientale o null
acknowledgement_prompt_modestringnoauto (predefinito) o manual (prodotti Storm-with-ack)
acknowledgement_promptstringnoUtilizzato quando acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullno5–120. Secondi di silenzio del chiamante prima di una richiesta di conferma
silence_max_checkinsinteger | nullno1–10
silence_checkins_enabledbooleannoPredefinito: true
connect_tone_enabledbooleannoPredefinito: false
pronunciation_dictsboolean | nullnoPredefinito: false; null equivale a false. Quando è true, la sintesi vocale Cartesia della chiamata utilizza il dizionario di pronuncia ThunderPhone per la lingua della chiamata, se disponibile. Funziona per le chiamate telefoniche e del widget. Si applica solo alle voci Cartesia di ThunderPhone, non alle voci associate alla tua chiave API Cartesia. Non viene utilizzato con agent_id, per il quale si applicano le impostazioni dell'agente salvato
voicemail_actionstringnoprompt (predefinito), hangup o message
voicemail_messagestringnoUtilizzato quando voicemail_action="message"
agent_namestringnoNome visualizzato riportato nelle dashboard e nel widget
org_namestringnoNome visualizzato dell'organizzazione per la persona dell'agente
toolsarraynoSchemi inline degli strumenti funzione (vedi Strumenti funzione)
transfer_destinationsarray di object | nullnoFino a 50 voci {name, number} a cui questa chiamata può essere trasferita, convalidate come i numeri di trasferimento di un agente. L'agente trasferisce in base al nome e solo verso queste destinazioni; [] indica nessun trasferimento. Ometti il campo o invia null per consentire all'agente di trasferire a un numero scelto da lui
timezonestringnoFuso orario IANA per questa chiamata, ad esempio America/Chicago. L'agente comunica la data e l'ora in questo fuso, anziché nel proprio fuso orario. Consentito anche accanto a agent_id. I valori che non sono fusi orari IANA vengono ignorati. Vedi Imposta il fuso orario per chiamata
call_idintegernoEco facoltativo dell'ID chiamata della richiesta; ignorato

Poiché prompt e voice sono obbligatori, restituire {} o qualsiasi risposta che non superi la convalida rifiuta la chiamata: una chiamata telefonica viene terminata come descritto in Timeout e tentativi, e una richiesta di sessione del widget non va a buon fine con 422. Non è disponibile alcun fallback a un agente statico in questo percorso (un numero o una chiave in modalità webhook non ha un agente assegnato).


Limite delle dimensioni della risposta


Esempio di gestore

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

Risposta con strumenti funzione

Allega strumenti affinché l'IA possa chiamare le tue API durante la conversazione:

{
  "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"
        }
      }
    }
  ]
}

Scheda rapida dei livelli di prodotto

ProdottoLatenzaRagionamentoConferma
sparkMinimaBase—
boltBassaMigliorato—
storm-baseMediaSolido—
storm-base-with-ackMediaSolidoRiempitivo automatico durante l'elaborazione
storm-extraPiù altaApprofondito—
storm-extra-with-ackPiù altaApprofonditoRiempitivo automatico durante l'elaborazione

Correlati