---
title: "Configuração dinâmica por chamada"
description: "Escolha o agente que atende — ou reescreva o prompt e as configurações dele — separadamente para cada chamada recebida, com base em lógica personalizada em um webhook que você controla."
---

Por padrão, cada número de telefone e chave publicável tem um agente estático
atribuído. Quando precisar de personalização **por quem liga** ou **por visitante**
— roteamento de VIPs, contexto de usuários autenticados, testes A/B de prompt — mude para
o modo webhook e deixe seu servidor decidir.

## Como funciona

1. Você assina o evento [`telephony.incoming`](/pt/webhooks/events)
   (telefone) ou [`web.incoming`](/pt/webhooks/events) (widget).
   Ambos são webhooks **bloqueantes**: o ThunderPhone aguarda sua
   resposta antes de continuar a chamada, por até 13 segundos em uma
   chamada telefônica (incluindo uma tentativa adicional) e 10 segundos em uma sessão de widget.
2. O ThunderPhone envia `{call_id, from_number, to_number}` (as sessões de
   widget incluem campos específicos do widget em vez de números — consulte o
   [esquema de solicitação](/pt/webhooks/call-incoming)).
3. Seu servidor responde com uma configuração do agente (prompt, voz,
   produto, ferramentas). O ThunderPhone usa essa configuração para a chamada.
4. Não há agente alternativo. Se você retornar `{}` ou uma configuração
   inválida, ou se a solicitação ainda falhar após a
   [tentativa adicional](/pt/webhooks/call-incoming), a chamada
   telefônica será encerrada imediatamente e registrada com `end_reason`
   `inbound_config_webhook_failed`; uma solicitação de sessão de widget falhará.

<Note>
  Funciona de forma idêntica para chamadas telefônicas (`telephony.incoming`) e sessões
  do widget (`web.incoming`), sejam entregues a um endpoint de webhook
  ou ao webhook legado de URL única.
</Note>

<Warning>
  **Nenhum aviso de consentimento do ThunderPhone é reproduzido em chamadas
  configuradas por webhook em linha.** Este caminho ignora o aviso de início de chamada
  no nível do agente e é expressamente excluído da estrutura de avisos de
  consentimento do ThunderPhone (Termos de Serviço, seção "Gravação e consentimento").
  Sua organização é responsável por todos os avisos e consentimentos sobre gravação,
  monitoramento, participação de IA e identificação de quem liga nessas chamadas. Forneça
  esses avisos em seu próprio fluxo — por exemplo, no script de abertura do prompt —
  antes de ativar o modo webhook em linha. Uma resposta de webhook que faça referência a um
  `agent_id` salvo usa a política normal de gravação e divulgação desse agente.
</Warning>

## 1. Configure o destino do webhook

<Tabs>
<Tab title="Chamadas telefônicas">
Para números de telefone, assine seu endpoint no evento `telephony.incoming`:

```bash
curl -X POST https://api.thunderphone.com/v1/developer/webhook-endpoints \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "label":  "Prod call-incoming",
    "url":    "https://example.com/thunderphone/incoming",
    "events": ["telephony.incoming"]
  }'
```

A resposta inclui um `secret` de uso único — salve-o; você o usará
para verificar a assinatura.
</Tab>
<Tab title="Widget web">
Para sessões do widget, crie uma chave publicável em `mode="webhook"`
com a URL do seu endpoint incorporada:

```bash
curl -X POST https://api.thunderphone.com/v1/publishable-key \
  -H "Authorization: Bearer sk_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name":            "Dynamic widget",
    "mode":            "webhook",
    "webhook_url":     "https://example.com/thunderphone/widget-incoming",
    "allowed_domains": ["example.com"]
  }'
```

O widget fará POST para esta URL no início de cada sessão.
</Tab>
</Tabs>

## 2. Implemente o handler

Três regras práticas:

- **Verifique a assinatura** em todas as solicitações (consulte
  [Verificar assinaturas de webhook](/pt/guides/verify-webhook-signatures)).
  Não pule esta etapa no desenvolvimento — faça corretamente uma vez e reutilize.
- **Responda rápido**. A primeira tentativa de uma chamada telefônica é interrompida após 10
  segundos, e toda a troca após 13; cada segundo é silêncio
  para quem liga. Faça consultas ao banco de dados se necessário, mas não chame
  LLMs downstream de forma síncrona — se quiser geração dinâmica de prompt,
  pré-calcule e armazene em cache.
- **Use um fallback adequado**. Para qualquer estado inesperado, responda com um
  `prompt` e uma `voice` padrão (ou um `agent_id` salvo). Retornar `{}`
  ou um status de erro encerra a chamada.

<CodeGroup>
```python FastAPI
import hashlib
import hmac
import json
import os

from fastapi import FastAPI, HTTPException, Request

app = FastAPI()
SECRET = os.environ["THUNDERPHONE_WEBHOOK_SECRET"]

def verify(body: bytes, sig: str) -> bool:
    expected = hmac.new(SECRET.encode(), body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig or "")

@app.post("/thunderphone/incoming")
async def incoming(request: Request):
    body = await request.body()
    if not verify(body, request.headers.get("X-ThunderPhone-Signature", "")):
        raise HTTPException(401)

    event = json.loads(body)
    if event["type"] not in ("telephony.incoming", "web.incoming"):
        return {}  # fall back to default

    caller = event["data"]["from_number"]
    # Cheap DB lookup: is this a known VIP?
    customer = lookup_customer(caller)
    if customer and customer.tier == "vip":
        return {
            "prompt":  f"You are a VIP concierge for {customer.name}. Be proactive…",
            "voice":   "john",
            "product": "storm-base",
        }
    return {}  # default agent handles non-VIPs

def lookup_customer(phone: str):
    # ... your CRM integration ...
    pass
```

```javascript Express
import crypto from "node:crypto";
import express from "express";

const app = express();
const SECRET = process.env.THUNDERPHONE_WEBHOOK_SECRET;

function verify(body, sig) {
  const expected = crypto.createHmac("sha256", SECRET).update(body).digest("hex");
  return sig &&
    crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

app.post(
  "/thunderphone/incoming",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    if (!verify(req.body, req.header("X-ThunderPhone-Signature"))) {
      return res.sendStatus(401);
    }
    const event = JSON.parse(req.body.toString("utf8"));

    const IMPORTANT_TYPES = new Set([
      "telephony.incoming",
      "web.incoming",
    ]);
    if (!IMPORTANT_TYPES.has(event.type)) return res.json({});

    const customer = await lookupCustomer(event.data.from_number);
    if (customer?.tier === "vip") {
      return res.json({
        prompt:  `You are a VIP concierge for ${customer.name}. Be proactive…`,
        voice:   "john",
        product: "storm-base",
      });
    }
    res.json({}); // fall back to default agent
  },
);
```
</CodeGroup>

## 3. Esquema de resposta

O corpo da resposta corresponde exatamente ao
[esquema de resposta de chamada recebida](/pt/webhooks/call-incoming).
Os campos mais usados:

| Campo | Tipo | Descrição |
|-------|------|-------------|
| `prompt` | string (obrigatório) | Prompt de sistema para o agente |
| `voice` | string (obrigatório) | ID de voz de [`GET /v1/voices`](/api-reference/agents#voices) |
| `product` | string | O padrão é `spark`. Produtos Bolt experimentais estão disponíveis apenas para organizações autorizadas. |
| `background_track` | string \| null | ID de áudio ambiente |
| `acknowledgement_prompt_mode` | string | `auto` ou `manual` (somente Storm com confirmação) |
| `acknowledgement_prompt` | string | Obrigatório quando o modo é `manual` |
| `tools` | array | Esquemas de ferramentas de função inline — consulte [Ferramentas de função](/pt/tools/overview) |

<Note>
  A ordem de fala por chamada e `max_hold_seconds` não estão disponíveis na
  resposta do webhook. Configure-os no
  [Agente](/api-reference/agents) referenciado.
</Note>

### Mantenha um agente salvo e forneça variáveis

Retorne `{"agent_id": 12, "variables": {"name": "Ada"}}` para usar o agente salvo
dessa organização com dados por chamada. O prompt dele pode conter `{{name}}` ou
`{{name|Friend}}`. As variáveis do webhook são mescladas sobre as variáveis no nível
da solicitação; null usa o padrão do placeholder ou texto vazio se nenhum for fornecido.
Os valores finais e os nomes não resolvidos aparecem nos detalhes da chamada e nos webhooks
de conclusão. Respostas de agentes salvos aceitam apenas `agent_id` e `variables`. Se
`prompt` estiver presente, a resposta usará configuração inline e ignorará `agent_id` (incluindo
metadados nulos ou não inteiros); o prompt inline ainda deve ser válido. Respostas de
configuração inline também podem incluir `variables`. Respostas de agentes salvos usam a
divisão A/B implantada do agente em chamadas por telefone e widget e, em seguida, renderizam
as variáveis. Consulte [variáveis de chamada](/pt/guides/call-variables)
para limites e suporte à API de sessão. A configuração bloqueante vem da URL legada de
número de telefone/organização ou de uma chave de widget no modo webhook; eventos recebidos
do sistema de endpoint são apenas notificações.

## Padrões

### Contexto de usuário conectado

Em widgets no modo webhook, a página do visitante já sabe quem ele
é. Chame seu webhook com um parâmetro de query string que o SDK do widget
encaminha (`?customer_id=123`) e busque o cliente no servidor.

### Lançamento de prompt A/B

Antes de implementar isso manualmente, observe que o ThunderPhone tem um recurso nativo de
[Experimentos](/pt/guides/concepts)
(`/dashboard/experiments` e a aba **A/B** do criador de agentes) que
define variantes, divide o tráfego e compara os resultados por variante —
sem necessidade de webhook.

Se ainda precisar de controle no lado do webhook: aplique hash em `call_id` → bucket;
sirva o prompt A para `0..49` e o prompt B para `50..99`. Registre qual
bucket você escolheu em seu próprio banco de dados e depois correlacione-o à avaliação
da chamada concluída.

### Roteamento baseado em horário

Horário comercial → agente de "suporte ao vivo"; fora do horário comercial → agente de "anotar uma mensagem".
Faça uma troca simples com `new Date().getUTCHours()` no seu manipulador.

---

## Próximas etapas

<CardGroup cols={2}>
  <Card title="Referência do webhook de chamada recebida" icon="phone" href="/pt/webhooks/call-incoming">
    Esquemas exatos de solicitação e resposta, incluindo todas as chaves de configuração.
  </Card>
  <Card title="Verificar assinaturas de webhook" icon="shield-check" href="/pt/guides/verify-webhook-signatures">
    Configure o HMAC corretamente uma vez; reutilize em todos os lugares.
  </Card>
  <Card title="Criar uma integração de ferramenta" icon="screwdriver-wrench" href="/pt/guides/build-tool-integration">
    Combine roteamento dinâmico com ferramentas por agente.
  </Card>
  <Card title="Semântica de entrega" icon="bolt" href="/pt/webhooks/overview">
    Tentativas, ordenação, tempos limite.
  </Card>
</CardGroup>
