ThunderPhone 2.0 ya está disponible.Empieza por tu cuenta desde 2¢/min.Lee el anuncio

Webhooks

telephony.incoming / web.incoming

Webhook bloqueante que configura una llamada entrante en tiempo real.

Cuando una llamada telefónica entrante llega a un número sin un agente asignado, o una sesión del widget web comienza con una clave publicable en mode="webhook", ThunderPhone envía una solicitud bloqueante telephony.incoming / web.incoming a tu URL de webhook heredada y espera hasta 10 segundos una respuesta de configuración. Usa este intercambio para elegir dinámicamente un prompt, una voz y herramientas por llamada: consulta la guía de configuración dinámica de llamadas para conocer el patrón integral.

El intercambio bloqueante no tiene alternativa: si tu controlador devuelve un estado distinto de 2xx, agota el tiempo de espera o devuelve una configuración que no supera la validación, la llamada se rechaza (la llamada telefónica no se conecta; la solicitud de sesión del widget falla con 502/422). Responde rápido: quien llama escucha el tono de llamada mientras decides.

Carga útil de la solicitud

Para llamadas telefónicas (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
CampoTipoDescripción
call_idintegerID de llamada: se mantiene estable en todos los eventos de esta llamada
from_numberstringNúmero E.164 de quien llama
to_numberstringDestino E.164 (uno de tus números de ThunderPhone)

Para sesiones del widget web (web.incoming), data identifica la página de inserción en lugar de números telefónicos:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
CampoTipoDescripción
call_idintegerID de llamada
origin_domainstringEl origen de la página que aloja el widget
publishable_key_prefixstringPrimeros caracteres de la clave publicable que abrió la sesión
language, primary_languagestringSe incluye cuando la sesión del widget solicitó una anulación de idioma
voicestringSe incluye cuando la sesión del widget solicitó una anulación de voz
website_contextstringSe incluye cuando el widget pasó contexto de página por sesión

Esquema de respuesta

Devuelve un objeto JSON que describa la configuración del agente para esta llamada. prompt y voice son obligatorios; todo lo demás es opcional.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
CampoTipoObligatorioDescripción
promptcadenaPrompt del sistema que dirige al agente
voicecadenaID de voz de GET /v1/voices, por ejemplo, john. Se acepta voice_name como alias. Las voces desconocidas no superan la validación y rechazan la llamada
productcadenanoEl valor predeterminado es spark. Permitidos: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelcadenanominimal, base (predeterminado) o extra. Se reemplaza para productos Storm: storm-extra* fuerza extra; los demás storm-* fuerzan base
audio_context_modecadenanofull (predeterminado) o reduced
watchdog_enabledbooleanonoActiva la supervisión para esta llamada. El valor predeterminado es false
additional_audio_contextbooleano | nulonoIncluye los últimos turnos de audio de quien llama en lugar de solo el turno más reciente, lo que mejora las correcciones y la recopilación de datos con mucha ortografía o números, con un pequeño impacto en la latencia y el costo. Se activa de forma predeterminada para sesiones entrantes y se desactiva para llamadas telefónicas salientes; null conserva el valor predeterminado
storm_feedback_modecadenanonone, acknowledgement (predeterminado) o tick
languagecadenanoAbreviatura de primary_language
primary_languagecadenanoCódigo de idioma, normalizado (predeterminado en). Los códigos que no se pueden resolver rechazan la llamada
has_additional_languagesbooleanonoEl valor predeterminado es false
additional_languagesarreglo de cadenasnoIdiomas adicionales a los que el agente puede cambiar
native_voice_switchingbooleanonoEl valor predeterminado es false. Cuando la llamada cambia a otro idioma, cambia a una voz nativa de ese idioma (según el género) en lugar de conservar la voz configurada
background_trackcadena | nulonoID de audio ambiental o null
acknowledgement_prompt_modecadenanoauto (predeterminado) o manual (productos Storm con confirmación)
acknowledgement_promptcadenanoSe usa cuando acknowledgement_prompt_mode="manual"
silence_interval_secondsentero | nulono5–120. Segundos de silencio de quien llama antes de una verificación
silence_max_checkinsentero | nulono1–10
silence_checkins_enabledbooleanonoEl valor predeterminado es true
connect_tone_enabledbooleanonoEl valor predeterminado es false
voicemail_actioncadenanoprompt (predeterminado), hangup o message
voicemail_messagecadenanoSe usa cuando voicemail_action="message"
agent_namecadenanoNombre visible que se muestra en los paneles y el widget
org_namecadenanoNombre visible de la organización para la personalidad del agente
toolsarreglonoEsquemas de herramientas de función en línea (consulta Herramientas de función)
call_identeronoEco opcional del ID de llamada de la solicitud; se ignora

Como prompt y voice son obligatorios, devolver {} o cualquier respuesta que no supere la validación rechaza la llamada con 422: no existe respaldo de agente estático en esta ruta (un número o clave en modo webhook no tiene un agente asignado).


Límite de tamaño de respuesta


Controlador de ejemplo

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

Respuesta con herramientas de función

Adjunta herramientas para que la IA pueda llamar a tus API durante la conversación:

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

Guía rápida de niveles de producto

ProductoLatenciaRazonamientoConfirmación
sparkLa más bajaBásico
boltBajaMejorado
storm-baseMediaSólido
storm-base-with-ackMediaSólidoTexto de relleno automático mientras piensa
storm-extraMás altaProfundo
storm-extra-with-ackMás altaProfundoTexto de relleno automático mientras piensa

Relacionado