ThunderPhone 2.0 ya está disponible.Empieza por tu cuenta desde 2¢/min.Lee el anuncio

Webhooks

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:

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 cuyo t difiera 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-Signature firma únicamente el cuerpo sin procesar. Se sigue enviando, sin cambios, para que los verificadores existentes sigan funcionando.

Pasos

  1. Lee el encabezado, el método, la ruta y la consulta sin procesar, y el cuerpo sin procesar antes de realizar cualquier análisis.
  2. Calcula el HMAC-SHA256 hexadecimal de <t>\n<METHOD>\n<path and query>\n<body> con tu secreto.
  3. Rechaza la solicitud si t difiere en más de 300 segundos o si el resultado no coincide con v1 en 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.

Python (Flask)
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 "", 204
Node.js (Express)
import 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ísticaHeredado (/v1/webhook)Endpoints (/v1/developer/webhook-endpoints)
Número de URL1 por organizaciónMuchas por organización
Cobertura de eventosSolo telephony.* / web.*Los 10 tipos de eventos
Filtro de eventos—Por endpoint
ReintentosNinguno, excepto uno para el intercambio de configuración de llamadas telefónicas8 intentos durante 24 h
Envoltoriotype + datatype + data + event_id
Rotación de secretosReemplaza un único secretoSecreto por endpoint
Desactivar sin eliminarPUT /v1/webhook con {"url": ""}status=disabled
Visibilidad del estado—active / disabled / failing
Intercambio de configuración bloqueanteSí (telephony.incoming / web.incoming)Nunca: solo notificaciones
Ideal paraConfiguración dinámica de llamadasConsumo 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