Visão geral dos webhooks
Como o ThunderPhone entrega eventos em tempo real, como verificar assinaturas e como os modelos de entrega legado e baseado em endpoints se comparam.
O ThunderPhone envia solicitações HTTP POST ao seu servidor quando algo
acontece durante uma chamada — uma chamada recebida começa, uma chamada termina, uma execução
de avaliação é concluída, um alerta é disparado e assim por diante. Há dois modelos
de entrega:
Várias URLs, segredos por endpoint, filtros de eventos por endpoint
e novas tentativas automáticas.
Gerencie por GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Uma URL por organização. Transmite os eventos do ciclo de vida da chamada, incluindo as
trocas de configuração bloqueantes. Gerenciado em GET/PUT /v1/webhook.
Todos os dez tipos de evento no catálogo de eventos são
entregues por endpoints de webhook. Os seis eventos do ciclo de vida da chamada
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) também são enviados ao
webhook legado de URL única — se você tiver uma URL legada e um endpoint
correspondente, receberá o evento nos dois caminhos. O comportamento
bloqueante (a troca de configuração de telephony.incoming / web.incoming
e o despacho de ferramentas no
modo webhook) existe exclusivamente no caminho legado; cada entrega ao endpoint é uma
notificação sem aguardar resposta.
Formato do payload
As entregas a endpoints são um objeto JSON com data, event_id e
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id é exclusivo para cada evento emitido. Ele é idêntico entre novas tentativas
e entre todos os endpoints que recebem o evento — faça a deduplicação por ele.
O webhook legado de URL única envia o mesmo type e data, mas
sem event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}Na transmissão, cada corpo é serializado canonicamente — chaves ordenadas alfabeticamente, sem espaços em branco, UTF-8. Os exemplos formatados nestes documentos servem apenas para facilitar a leitura.
Consulte o Catálogo de eventos para ver a lista completa de tipos de evento e campos de payload.
Verificação de assinatura
Cada solicitação contém uma assinatura HMAC-SHA256 sobre o corpo bruto da solicitação no cabeçalho X-ThunderPhone-Signature. A chave de assinatura é o secret do endpoint (ou o secret de webhook no nível da sua organização para entregas legadas).
Etapas
- Leia o corpo bruto da solicitação antes de qualquer análise.
- Calcule
hmac_sha256(secret, body).hexdigest(). - Compare em tempo constante com o cabeçalho
X-ThunderPhone-Signature.
Assinamos exatamente os bytes que transmitimos, e esses bytes são a serialização JSON canônica (chaves ordenadas, separadores compactos). Portanto, verificar em relação ao corpo bruto sempre funciona — e, se o seu framework fornecer apenas o JSON analisado, serializá-lo novamente com chaves ordenadas e separadores compactos produzirá bytes idênticos. Ambas as abordagens são abordadas no guia de verificação.
import hmac
import hashlib
def verify_signature(body: bytes, signature: str, secret: str) -> bool:
expected = hmac.new(
secret.encode("utf-8"),
body,
hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected, signature or "")
# Example Flask handler
from flask import Flask, request, abort
app = Flask(__name__)
@app.post("/thunderphone-webhook")
def handle():
body = request.get_data()
sig = request.headers.get("X-ThunderPhone-Signature", "")
if not verify_signature(body, sig, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
# dispatch on event["type"] …
return "", 204import crypto from "node:crypto";
import express from "express";
function verifySignature(body, signature, secret) {
const expected = crypto
.createHmac("sha256", secret)
.update(body)
.digest("hex");
if (!signature || expected.length !== signature.length) return false;
return crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(signature),
);
}
const app = express();
app.post(
"/thunderphone-webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const sig = req.header("X-ThunderPhone-Signature") || "";
if (!verifySignature(req.body, sig, process.env.WEBHOOK_SECRET)) {
return res.sendStatus(401);
}
const event = JSON.parse(req.body.toString("utf8"));
// dispatch on event.type …
res.sendStatus(204);
},
);Semântica de entrega
Estas semânticas se aplicam a entregas de endpoint. O webhook legado de URL única é uma única tentativa síncrona sem novas tentativas.
Novas tentativas
Cada evento é tentado uma vez imediatamente. Qualquer resposta 2xx
confirma a entrega. Em qualquer outro resultado (não-2xx,
erro de conexão, tempo esgotado), tentamos novamente 1 min, 5 min, 30 min, 2 h, 6 h,
12 h e 24 h após a primeira tentativa — 8 tentativas ao longo de
24 horas. Se todas as tentativas falharem, a entrega é interrompida e o endpoint
recebe a marcação status="failing" em
endpoints de webhook. Retorne 2xx assim que
a carga for aceita de forma durável; processe de modo assíncrono.
Ordenação
A ordenação das entregas é feita conforme o possível. Na prática, entregamos na
ordem em que os eventos são emitidos, mas novas tentativas podem reordenar eventos em caso de falha.
Sempre elimine duplicatas e reconcilie por call_id / id do objeto.
Duplicatas
A entrega é pelo menos uma vez: uma nova tentativa após uma resposta que nunca
vimos pode duplicar um evento. Cada nova tentativa inclui o mesmo
event_id, portanto armazene os ids processados e ignore repetições. event_id também
é compartilhado entre endpoints — dois endpoints inscritos no
mesmo evento recebem o mesmo event_id.
Tempos limite
As entregas de endpoint têm um tempo limite de 30 s por tentativa. No
caminho legado, as solicitações bloqueantes que determinam o comportamento de chamadas ao vivo — a
troca de configuração telephony.incoming / web.incoming —
expiram após 10 s, mas uma resposta lenta atrasa o atendimento da chamada,
portanto procure responder em poucos segundos. O despacho de ferramentas no modo webhook permite 20 s
por padrão, e as declarações de ferramentas podem definir um timeout de nível superior.
IPs de origem
Os webhooks de saída são originados na faixa de IPs de nuvem do ThunderPhone. Se seu firewall exigir uma lista de permissões, entre em contato com o suporte e compartilharemos as faixas atuais.
Escolha entre webhooks legados e baseados em endpoints
| Recurso | Legado (/v1/webhook) | Endpoints (/v1/developer/webhook-endpoints) |
|---|---|---|
| Número de URLs | 1 por organização | Vários por organização |
| Cobertura de eventos | Apenas telephony.* / web.* | Todos os 10 tipos de evento |
| Filtro de eventos | — | Por endpoint |
| Novas tentativas | Nenhuma | 8 tentativas em 24 h |
| Envelope | type + data | type + data + event_id |
| Rotação de segredo | Substitui o segredo único | Segredo por endpoint |
| Desativar sem excluir | — | status=disabled |
| Visibilidade de status | — | active / disabled / failing |
| Troca de configuração bloqueante | Sim (telephony.incoming / web.incoming) | Nunca — apenas notificações |
| Ideal para | Configuração dinâmica de chamadas | Consumo de eventos em produção |
Novas integrações devem consumir eventos por meio de webhooks baseados em endpoints. Mantenha (ou adicione) uma URL legada apenas se você configurar chamadas dinamicamente no momento do atendimento ou usar o despacho de ferramentas no modo webhook — essas trocas de solicitação/resposta são executadas apenas no caminho legado.
Relacionados
Todos os tipos de evento e suas cargas.
Gerencie vários endpoints, filtros de eventos e segredos.
A solicitação bloqueante que seu servidor deve responder para configurar chamadas.
Carga pós-chamada com transcrição, gravação e métricas.