Open in
Descripción general de los webhooks
Cómo ThunderPhone entrega eventos en tiempo real, cómo verificar firmas y cómo se comparan los modelos de entrega heredado y basado en endpoints.
ThunderPhone envía solicitudes HTTP POST a tu servidor cuando ocurren eventos durante una llamada: inicia una llamada entrante, termina una llamada, finaliza una ejecución de evaluación, se activa una alerta, entre otros. Hay dos modelos de entrega:
Varias URL, secretos por endpoint, filtros de eventos por endpoint
y reintentos automáticos.
Adminístralos mediante GET/POST/PATCH/DELETE /v1/developer/webhook-endpoints.
Una URL por organización. Incluye los eventos del ciclo de vida de las llamadas, incluidos los intercambios de configuración
bloqueantes. Se administra en GET/PUT /v1/webhook.
Todos los tipos de eventos enumerados en el catálogo de eventos se entregan mediante endpoints de webhook. Los seis eventos del ciclo de vida de las llamadas
(telephony.incoming, telephony.complete, telephony.tool,
web.incoming, web.complete, web.tool) también se envían al
webhook heredado de una sola URL; si tienes tanto una URL heredada como un endpoint coincidente, recibes el evento en ambas rutas. El comportamiento bloqueante (el intercambio de configuración de
telephony.incoming / web.incoming y el
despacho de herramientas en modo webhook) existe exclusivamente en la ruta heredada; cada entrega a un endpoint es una notificación sin esperar respuesta.
Formato de la carga útil
Las entregas a endpoints son un objeto JSON con data, event_id y
type:
{
"data": {
"call_id": 987654321,
"from_number": "+14155550199",
"to_number": "+15551234567"
},
"event_id": "3f6b2ad0-1c9e-4a57-9f2b-8f6f0f9d2f11",
"type": "telephony.incoming"
}event_id es único para cada evento emitido. Es idéntico en todos los reintentos
y en cada endpoint que recibe el evento; úsalo para deduplicar.
El webhook heredado de una sola URL envía el mismo type y data, pero
sin event_id:
{
"type": "telephony.incoming",
"data": { "call_id": 987654321, "from_number": "+14155550199", "to_number": "+15551234567" }
}En la transmisión, cada cuerpo se serializa de forma canónica: claves ordenadas alfabéticamente, sin espacios en blanco, UTF-8. Los ejemplos con formato legible de esta documentación son solo para facilitar la lectura.
Consulta el catálogo de eventos para ver la lista completa de tipos de eventos y campos de carga útil.
Verificación de firma
Cada solicitud incluye dos firmas, ambas HMAC-SHA256 con la secret del
endpoint como clave (o la secret de webhook de tu organización para entregas
heredadas):
X-ThunderPhone-Signature-V2: t=<unix seconds>,v1=<hex>firma la marca de tiempo, el método, la ruta y consulta, y el cuerpo sin procesar. Verifica esta firma y rechaza las solicitudes cuyotdifiera en más de 5 minutos de tu reloj para evitar que una solicitud capturada pueda reproducirse más tarde. Los reintentos se firman nuevamente cuando se envían.X-ThunderPhone-Signaturefirma únicamente el cuerpo sin procesar. Se sigue enviando, sin cambios, para que los verificadores existentes sigan funcionando.
Pasos
- Lee el encabezado, el método, la ruta y la consulta sin procesar, y el cuerpo sin procesar antes de realizar cualquier análisis.
- Calcula el HMAC-SHA256 hexadecimal de
<t>\n<METHOD>\n<path and query>\n<body>con tu secreto. - Rechaza la solicitud si
tdifiere en más de 300 segundos o si el resultado no coincide conv1en una comparación de tiempo constante.
Los cuerpos son la serialización JSON canónica (claves ordenadas, separadores compactos), y firmamos exactamente los bytes que transmitimos. La guía de verificación incluye la cadena firmada exacta, un ejemplo práctico, código sin el SDK y el encabezado heredado.
from flask import Flask, abort, request
from thunderphone import verify_signature
app = Flask(__name__)
@app.post("/thunderphone-webhook")
def handle():
query = request.query_string.decode()
if not verify_signature(
request.headers.get("X-ThunderPhone-Signature-V2"),
secret=WEBHOOK_SECRET,
method=request.method,
path=request.path + (f"?{query}" if query else ""),
body=request.get_data(), # raw bytes
):
abort(401)
event = request.get_json()
# dispatch on event["type"] …
return "", 204import express from "express";
import { verifySignature } from "@thunderphone/sdk";
const app = express();
app.post(
"/thunderphone-webhook",
express.raw({ type: "application/json" }),
(req, res) => {
const ok = verifySignature({
header: req.header("X-ThunderPhone-Signature-V2"),
secret: process.env.WEBHOOK_SECRET,
method: req.method,
path: req.originalUrl,
body: req.body,
});
if (!ok) 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 aplican a las entregas de puntos de conexión. El
webhook heredado de una sola URL realiza un único intento síncrono sin reintentos, excepto el
intercambio de configuración de llamada telefónica
telephony.incoming,
que se reintenta una vez.
Reintentos
Cada evento se intenta una vez de inmediato. Cualquier respuesta 2xx
confirma la entrega. Ante cualquier otro resultado (que no sea 2xx,
error de conexión o tiempo de espera agotado), reintentamos a los 1 m, 5 m, 30 m, 2 h, 6 h,
12 h y 24 h después del primer intento — 8 intentos durante
24 horas. Si todos los intentos fallan, la entrega se detiene y el punto de conexión
se marca como status="failing" en
puntos de conexión de webhooks; enviamos un correo electrónico a los
propietarios y administradores de tu organización y lo mostramos en el panel. Devuelve 2xx tan pronto como
la carga útil se acepte de forma duradera; procésala de forma asíncrona.
Los envíos de prueba desde la API de prueba del punto de conexión reciben un reintento aproximadamente un minuto después
del primer intento y nunca cambian el estado del punto de conexión.
Orden
El orden de entrega se realiza según el mejor esfuerzo. En la práctica entregamos los eventos
en el orden en que se emiten, pero los reintentos pueden cambiar el orden cuando hay fallos.
Siempre elimina duplicados y reconcilia mediante call_id / id de objeto.
Duplicados
La entrega es al menos una vez: un reintento después de una respuesta que nunca
recibimos puede duplicar un evento. Cada reintento incluye el mismo
event_id, así que almacena los ids procesados y omite las repeticiones. event_id también
se comparte entre endpoints: dos endpoints suscritos al
mismo evento reciben el mismo event_id.
Tiempos de espera
Las entregas de puntos de conexión tienen un tiempo de espera de 30 s por intento. En la
ruta heredada, las solicitudes bloqueantes que controlan el comportamiento de llamadas en vivo — el
intercambio de configuración de
telephony.incoming / web.incoming —
están limitadas a 10 s para el primer intento de una llamada telefónica y a 13 s
para todo el intercambio, incluido su único reintento, y a 10 s para una sesión de widget. Una respuesta lenta retrasa
la atención de la llamada, así que intenta responder en un par de segundos. El despacho de herramientas en modo webhook permite 20 s
de forma predeterminada, y las declaraciones de herramientas pueden establecer un timeout de nivel superior.
IPs de origen
Los webhooks salientes se originan en el rango de IP de la nube de ThunderPhone. Si tu firewall requiere una lista de permitidos, contacta a soporte y compartiremos los rangos actuales.
Elegir entre webhooks heredados y basados en endpoints
| Característica | Heredado (/v1/webhook) | Endpoints (/v1/developer/webhook-endpoints) |
|---|---|---|
| Número de URL | 1 por organización | Muchas por organización |
| Cobertura de eventos | Solo telephony.* / web.* | Los 10 tipos de eventos |
| Filtro de eventos | — | Por endpoint |
| Reintentos | Ninguno, excepto uno para el intercambio de configuración de llamadas telefónicas | 8 intentos durante 24 h |
| Envoltorio | type + data | type + data + event_id |
| Rotación de secretos | Reemplaza un único secreto | Secreto por endpoint |
| Desactivar sin eliminar | PUT /v1/webhook con {"url": ""} | status=disabled |
| Visibilidad del estado | — | active / disabled / failing |
| Intercambio de configuración bloqueante | Sí (telephony.incoming / web.incoming) | Nunca: solo notificaciones |
| Ideal para | Configuración dinámica de llamadas | Consumo de eventos en producción |
Las nuevas integraciones deben consumir eventos mediante webhooks basados en endpoints. Conserva (o agrega) una URL heredada solo si configuras las llamadas dinámicamente al momento de atenderlas o usas el despacho de herramientas en modo webhook: esos intercambios de solicitud/respuesta solo se ejecutan en la ruta heredada.
Relacionado
Todos los tipos de eventos y sus cargas útiles.
Administra múltiples endpoints, filtros de eventos y secretos.
La solicitud bloqueante que tu servidor debe responder para configurar llamadas.
Carga útil posterior a la llamada con transcripción, grabación y métricas.