ThunderPhone 2.0 è arrivato.Parti in autonomia, 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 entrata raggiunge un numero senza un agente assegnato, oppure una sessione del widget web viene avviata su una chiave pubblicabile in mode="webhook", ThunderPhone invia una richiesta telephony.incoming / web.incoming bloccante al tuo URL webhook legacy e attende fino a 10 secondi una risposta di configurazione. 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 flusso completo.

Lo scambio bloccante non ha fallback: se il tuo handler restituisce uno stato non 2xx, supera il timeout o restituisce una configurazione che non supera la convalida, la chiamata viene rifiutata (la chiamata telefonica non si connette; la richiesta della sessione widget non riesce con 502/422). Rispondi rapidamente — il chiamante sente il tono di chiamata mentre decidi.

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 della chiamata
origin_domainstringL'origine della pagina che ospita il widget
publishable_key_prefixstringPrimi caratteri della chiave pubblicabile che ha aperto la sessione
language, primary_languagestringPresente quando la sessione widget ha richiesto un override della lingua
voicestringPresente quando la sessione widget ha richiesto un override della voce
website_contextstringPresente quando il widget ha passato il contesto della pagina per sessione

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
promptstringPrompt di sistema che guida l'agente
voicestringID voce da GET /v1/voices, ad esempio john. voice_name è accettato come alias. Le voci sconosciute non superano la convalida e rifiutano la chiamata
productstringnoIl valore predefinito è spark. 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* forza extra, gli altri storm-* forzano base
audio_context_modestringnofull (predefinito) o reduced
watchdog_enabledbooleannoAbilita la supervisione per questa chiamata. Valore predefinito false
additional_audio_contextboolean | nullnoIncludi gli ultimi turni dell'audio del chiamante anziché solo il turno più recente, migliorando le correzioni e la raccolta di dati ricca 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_languagesbooleannoValore predefinito false
additional_languagesarray di stringnoLingue aggiuntive a cui l'agente può passare
native_voice_switchingbooleannoValore predefinito false. Quando la chiamata passa a un'altra lingua, sostituisci la voce con una voce nativa di quella lingua (abbinata per genere) anziché mantenere la voce configurata
background_trackstring | nullnoID 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 verifica
silence_max_checkinsinteger | nullno1–10
silence_checkins_enabledbooleannoValore predefinito true
connect_tone_enabledbooleannoValore predefinito false
voicemail_actionstringnoprompt (predefinito), hangup o message
voicemail_messagestringnoUtilizzato quando voicemail_action="message"
agent_namestringnoNome visualizzato segnalato alle dashboard e al widget
org_namestringnoNome visualizzato dell'organizzazione per la persona dell'agente
toolsarraynoSchemi inline degli strumenti funzione (vedi Strumenti funzione)
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 con 422: non esiste un fallback dell'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