ThunderPhone 2.0 já está no ar.Comece por conta própria, a partir de 2¢/min.Leia o anúncio

Webhooks

telephony.incoming / web.incoming

Webhook bloqueante que define a configuração de uma chamada recebida em tempo real.

Quando uma chamada telefônica recebida chega a um número sem um agente atribuído, ou uma sessão de widget web é iniciada com uma chave publicável em mode="webhook", o ThunderPhone envia uma solicitação bloqueante telephony.incoming / web.incoming para sua URL de webhook legada e aguarda até 10 segundos por uma resposta de configuração. Use essa troca para escolher dinamicamente um prompt, uma voz e ferramentas por chamada — consulte o guia de configuração dinâmica de chamadas para conhecer o padrão completo.

A troca bloqueante não tem alternativa: se seu handler retornar um status diferente de 2xx, exceder o tempo limite ou retornar uma configuração que não passe na validação, a chamada será rejeitada (a chamada telefônica não será conectada; a solicitação da sessão do widget falhará com 502/422). Responda rapidamente — quem liga ouve o tom de chamada enquanto você decide.

Payload da solicitação

Para chamadas telefônicas (telephony.incoming):

{
  "type": "telephony.incoming",
  "data": {
    "call_id":     987654321,
    "from_number": "+14155550199",
    "to_number":   "+15551234567"
  }
}
CampoTipoDescrição
call_idinteiroID da chamada — estável em todos os eventos desta chamada
from_numberstringNúmero E.164 de quem liga
to_numberstringDestino E.164 (um dos seus números ThunderPhone)

Para sessões de widget web (web.incoming), o data identifica a página de incorporação em vez de números de telefone:

{
  "type": "web.incoming",
  "data": {
    "call_id": 987654322,
    "origin_domain": "https://example.com",
    "publishable_key_prefix": "pk_live_a1b2"
  }
}
CampoTipoDescrição
call_idinteiroID da chamada
origin_domainstringA origem da página que hospeda o widget
publishable_key_prefixstringPrimeiros caracteres da chave publicável que abriu a sessão
language, primary_languagestringPresente quando a sessão do widget solicitou uma substituição de idioma
voicestringPresente quando a sessão do widget solicitou uma substituição de voz
website_contextstringPresente quando o widget transmitiu contexto de página por sessão

Esquema de resposta

Retorne um objeto JSON que descreva a configuração do agente para esta chamada. prompt e voice são obrigatórios; todos os demais campos são opcionais.

{
  "prompt":  "You are a helpful booking assistant for Acme Restaurant.",
  "voice":   "john",
  "product": "spark",
  "background_track": null,
  "tools":   []
}
CampoTipoObrigatórioDescrição
promptstringsimPrompt de sistema que orienta o agente
voicestringsimID de voz de GET /v1/voices, por exemplo, john. voice_name é aceito como alias. Vozes desconhecidas falham na validação e rejeitam a chamada
productstringnãoO padrão é spark. Permitidos: spark, bolt, storm-base, storm-base-with-ack, storm-extra, storm-extra-with-ack
thinking_levelstringnãominimal, base (padrão) ou extra. Substituído para produtos Storm: storm-extra* força extra, outros storm-* forçam base
audio_context_modestringnãofull (padrão) ou reduced
watchdog_enabledbooleannãoAtiva a supervisão para esta chamada. O padrão é false
additional_audio_contextboolean | nullnãoInclui as últimas interações do áudio de quem liga em vez de apenas a interação mais recente, melhorando correções e a coleta de dados com muita soletração/números, com um pequeno impacto na latência/custo. É ativado por padrão para sessões recebidas e desativado para chamadas telefônicas de saída; null mantém o padrão
storm_feedback_modestringnãonone, acknowledgement (padrão) ou tick
languagestringnãoAbreviação para primary_language
primary_languagestringnãoCódigo de idioma, normalizado (padrão en). Códigos que não podem ser resolvidos rejeitam a chamada
has_additional_languagesbooleannãoO padrão é false
additional_languagesarray of stringnãoIdiomas adicionais para os quais o agente pode alternar
native_voice_switchingbooleannãoO padrão é false. Quando a chamada alterna para outro idioma, troca para uma voz nativa desse idioma (correspondente por gênero) em vez de manter a voz configurada
background_trackstring | nullnãoID de áudio ambiente ou null
acknowledgement_prompt_modestringnãoauto (padrão) ou manual (produtos Storm com confirmação)
acknowledgement_promptstringnãoUsado quando acknowledgement_prompt_mode="manual"
silence_interval_secondsinteger | nullnão5–120. Segundos de silêncio de quem liga antes de uma verificação
silence_max_checkinsinteger | nullnão1–10
silence_checkins_enabledbooleannãoO padrão é true
connect_tone_enabledbooleannãoO padrão é false
voicemail_actionstringnãoprompt (padrão), hangup ou message
voicemail_messagestringnãoUsado quando voicemail_action="message"
agent_namestringnãoNome de exibição informado aos painéis e ao widget
org_namestringnãoNome de exibição da organização para a persona do agente
toolsarraynãoEsquemas inline de ferramentas de função (consulte Ferramentas de função)
call_idintegernãoEco opcional do ID da chamada da solicitação; ignorado

Como prompt e voice são obrigatórios, retornar {} ou qualquer resposta que falhe na validação rejeita a chamada com 422 — não há fallback de agente estático neste caminho (um número ou chave no modo webhook não tem um agente atribuído).


Limite de tamanho da resposta


Exemplo de manipulador

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

Resposta com ferramentas de função

Anexe ferramentas para que a IA possa chamar suas APIs durante a conversa:

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

Guia rápido de níveis de produto

ProdutoLatênciaRaciocínioConfirmação
sparkMais baixaBásico
boltBaixaAprimorado
storm-baseMédiaForte
storm-base-with-ackMédiaFortePreenchimento automático enquanto pensa
storm-extraMais altaProfundo
storm-extra-with-ackMais altaProfundoPreenchimento automático enquanto pensa

Relacionados