ThunderPhone 2.0 já está no ar.Contrate online, 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 em uma chave publicável no mode="webhook", o ThunderPhone envia uma solicitação bloqueante telephony.incoming / web.incoming para sua URL de webhook legada e aguarda uma resposta de configuração: até 13 segundos para uma chamada telefônica, incluindo uma tentativa adicional, e até 10 segundos para uma sessão de widget (consulte Tempos limite e novas tentativas). Use este intercâmbio 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.

Tempos limite e novas tentativas

Responda rápido: quem liga está aguardando na linha enquanto você decide.

Para uma chamada telefônica (telephony.incoming):

  • A primeira tentativa pode levar até 10 segundos.
  • Se exceder o tempo limite, não conseguir conectar ou receber uma resposta 5xx, 408 ou 429, o ThunderPhone tenta mais uma vez. A nova tentativa recebe o tempo restante de um orçamento de 13 segundos para toda a troca: cerca de 3 segundos após uma primeira tentativa que excedeu o tempo limite, quase todos os 13 após uma falha rápida. Não há nova tentativa quando restariam menos de 2 segundos.
  • Qualquer outra resposta 4xx, uma resposta acima do limite de tamanho ou uma resposta 2xx cujo corpo não seja uma configuração válida é final e não gera nova tentativa.
  • Ambas as tentativas enviam o mesmo corpo, a mesma X-ThunderPhone-Signature e o mesmo cabeçalho X-ThunderPhone-Idempotency-Key (telephony-incoming-<call_id>), para que um manipulador com efeitos colaterais possa reconhecer a nova tentativa.

Não há agente de fallback. Se nenhuma configuração utilizável chegar, a chamada é encerrada imediatamente, em vez de deixar quem liga em uma linha silenciosa. Ela aparece no histórico de chamadas como uma chamada com falha e não faturada, com end_reason inbound_config_webhook_failed, e o evento usual telephony.complete é enviado.

Uma sessão de widget (web.incoming) recebe uma única tentativa de até 10 segundos. Se ela falhar, a solicitação de sessão falha com 502 ou 422.

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, conforme enviada pelo navegador. Um cliente que não seja um navegador pode enviar qualquer valor, portanto não o trate como prova da origem da solicitação
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 da página por sessão

Os campos de substituição (language, voice, website_context e quaisquer variables do navegador) chegam somente quando a chave publicável os permite em suas substituições do navegador. Uma solicitação de sessão que contenha uma substituição não permitida pela chave é rejeitada antes que este webhook seja chamado.


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 da voz de GET /v1/voices, por exemplo, john. voice_name é aceito como um 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_enabledbooleanonãoAtiva a supervisão desta chamada. O padrão é false
additional_audio_contextbooleano | nullnãoInclui os últimos turnos do áudio de quem liga, em vez de apenas o turno mais recente, melhorando correções e a coleta de dados com muita soletração ou números, com pequeno impacto na latência e no custo. É ativado por padrão para sessões de entrada 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 de primary_language
primary_languagestringnãoCódigo de idioma normalizado (o padrão é en). Códigos que não podem ser resolvidos rejeitam a chamada
has_additional_languagesbooleanonãoO padrão é false
additional_languagesarray de stringsnãoIdiomas adicionais para os quais o agente pode alternar
native_voice_switchingbooleanonãoO padrão é false. Quando a chamada alterna para outro idioma, troca para uma voz nativa desse idioma (correspondente ao gênero) em vez de manter a voz configurada
language_switchingstring | nullnãoask_first (padrão) ou follow_caller; null é igual a ask_first. Define como um agente multilíngue lida com a outra pessoa falando outro de seus idiomas. ask_first alterna imediatamente quando a pessoa pede o idioma ou diz uma frase completa nele e pergunta se deve continuar nesse idioma após algo curto, como um "sim". follow_caller alterna para qualquer idioma configurado que a pessoa fale, mesmo que seja uma única palavra. Aplica-se apenas quando additional_languages está definido
background_trackstring | nullnãoID de áudio ambiente ou null
acknowledgement_prompt_modestringnãoauto (padrão) ou manual (produtos Storm-with-ack)
acknowledgement_promptstringnãoUsado quando acknowledgement_prompt_mode="manual"
silence_interval_secondsinteiro | nullnão5–120. Segundos de silêncio de quem liga antes de uma verificação
silence_max_checkinsinteiro | nullnão1–10
silence_checkins_enabledbooleanonãoO padrão é true
connect_tone_enabledbooleanonãoO padrão é false
pronunciation_dictsbooleano | nullnãoO padrão é false; null é igual a false. Quando true, a fala da Cartesia na chamada usa o dicionário de pronúncia do ThunderPhone para o idioma da chamada, quando houver um disponível. Funciona para chamadas telefônicas e de widget. Aplica-se apenas às vozes Cartesia do próprio ThunderPhone, não às vozes na sua própria chave de API da Cartesia. Não é usado com agent_id, em que as configurações salvas do agente se aplicam
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 embutidos de ferramentas de função (consulte Ferramentas de função)
transfer_destinationsarray de objetos | nullnãoAté 50 entradas {name, number} para as quais esta chamada pode ser transferida, validadas como os números de transferência de um agente. O agente transfere pelo nome e somente para esses destinos; [] significa nenhuma transferência. Omita esse campo ou envie null para permitir que o agente transfira para um número que escolher
timezonestringnãoFuso horário IANA para esta chamada, como America/Chicago. O agente informa a data e a hora nesse fuso, em vez de usar o próprio fuso horário. Também permitido ao lado de agent_id. Valores que não são fusos IANA são ignorados. Consulte Definir o fuso horário por chamada
call_idinteironã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: uma chamada telefônica é encerrada conforme descrito em Tempos limite e novas tentativas, e uma solicitação de sessão do widget falha com 422. Não há alternativa para agente estático neste caminho (um número ou uma chave no modo webhook não tem 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