> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thunderphone.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Descripción general de 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, finaliza una llamada, se completa
una ejecución de evaluación, se activa una alerta, etc. Hay **dos modelos
de entrega**:

<CardGroup cols={2}>
  <Card title="Endpoints de webhook (recomendado)" icon="bolt" href="/es/webhooks/endpoints">
    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`.
  </Card>

  <Card title="Webhook heredado de URL única" icon="link" href="/api-reference/organizations#legacy-single-url-webhook">
    Una URL por organización. Incluye los eventos del ciclo de vida de las llamadas,
    incluidos los intercambios de configuración **bloqueantes**. Se administra mediante `GET/PUT /v1/webhook`.
  </Card>
</CardGroup>

Los diez tipos de eventos del [catálogo de eventos](/es/webhooks/events) 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 URL única: si tienes una URL heredada y un endpoint
coincidente, recibes el evento en **ambas** rutas. El comportamiento bloqueante
(el [intercambio de configuración de `telephony.incoming` / `web.incoming`](/es/webhooks/call-incoming)
y el [despacho de herramientas](/es/tools/overview) en modo webhook)
existe exclusivamente en la ruta heredada; cada entrega a un endpoint es una
notificación enviada sin esperar respuesta.

## Formato de la carga útil

Las entregas a endpoints son un objeto JSON con `data`, `event_id` y
`type`:

```json theme={null}
{
  "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 entre reintentos
**y** entre todos los endpoints que reciben el evento: úsalo para eliminar duplicados.

El webhook heredado de URL única envía el mismo `type` y `data`, pero
**sin** `event_id`:

```json theme={null}
{
  "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 en esta documentación son solo para facilitar la lectura.

Consulta el [Catálogo de eventos](/es/webhooks/events) para ver la lista completa de tipos
de eventos y campos de carga útil.

## Verificación de firmas

Cada solicitud incluye una firma HMAC-SHA256 sobre el **cuerpo sin procesar
de la solicitud** en el encabezado `X-ThunderPhone-Signature`. La clave de firma es el
`secret` del endpoint (o el `secret` de webhook a nivel de tu organización para entregas
heredadas).

### Pasos

1. Lee el cuerpo sin procesar de la solicitud **antes** de realizar cualquier análisis.
2. Calcula `hmac_sha256(secret, body).hexdigest()`.
3. Compara en tiempo constante con el encabezado `X-ThunderPhone-Signature`.

Firmamos exactamente los bytes que transmitimos, y esos bytes son la
serialización JSON canónica (claves ordenadas, separadores compactos). Por lo tanto,
verificar con el cuerpo sin procesar siempre funciona; y si tu framework
solo te proporciona JSON analizado, volver a serializarlo con claves ordenadas y
separadores compactos produce bytes idénticos. Ambas recetas se incluyen
en la [guía de verificación](/es/guides/verify-webhook-signatures).

<CodeGroup>
  ```python Python theme={null}
  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 "", 204
  ```

  ```javascript Node.js (Express) theme={null}
  import 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);
    },
  );
  ```
</CodeGroup>

## Semántica de entrega

Estas semánticas se aplican a las entregas de **endpoint**. El webhook
heredado de una sola URL realiza un único intento sincrónico sin reintentos.

<AccordionGroup>
  <Accordion title="Reintentos">
    Cada evento se intenta una vez de inmediato. Cualquier respuesta `2xx`
    confirma la entrega. Ante cualquier otro resultado (distinto de 2xx,
    error de conexión, tiempo de espera), reintentamos **1 min, 5 min, 30 min, 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 endpoint
    se marca con `status="failing"` en
    [endpoints de webhook](/es/webhooks/endpoints). Devuelve `2xx` tan pronto como
    la carga útil se acepte de forma duradera; procésala de forma asíncrona.
  </Accordion>

  <Accordion title="Orden">
    El orden de entrega se realiza según el mejor esfuerzo. En la práctica, entregamos en el
    orden en que se emiten los eventos, pero los reintentos pueden alterar el orden en caso de error.
    Siempre elimina duplicados y reconcilia mediante `call_id` / id de objeto.
  </Accordion>

  <Accordion title="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`.
  </Accordion>

  <Accordion title="Tiempos de espera">
    Las entregas a endpoints 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 [`telephony.incoming` / `web.incoming`](/es/webhooks/call-incoming)—
    agotan el tiempo de espera después de **10 s**, pero una respuesta lenta retrasa que se conteste la
    llamada, así que procura responder en un par de segundos.
    La [ejecución de herramientas](/es/tools/overview) en modo webhook permite 20 s.
  </Accordion>

  <Accordion title="IPs de origen">
    Los webhooks salientes se originan desde el rango de IP en la nube de ThunderPhone.
    Si tu firewall requiere una lista de permitidos, contacta al soporte y
    compartiremos los rangos actuales.
  </Accordion>
</AccordionGroup>

## Elegir entre webhooks heredados y basados en endpoints

| Función                                 | Heredado (`/v1/webhook`)                                                 | Endpoints (`/v1/developer/webhook-endpoints`) |
| --------------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- |
| Cantidad de URL                         | 1 por organización                                                       | Varias por organización                       |
| Cobertura de eventos                    | Solo `telephony.*` / `web.*`                                             | Los 10 tipos de eventos                       |
| Filtro de eventos                       | —                                                                        | Por endpoint                                  |
| Reintentos                              | Ninguno                                                                  | 8 intentos durante 24 h                       |
| Envoltorio                              | `type` + `data`                                                          | `type` + `data` + `event_id`                  |
| Rotación de secretos                    | Reemplaza el secreto único                                               | Secreto por endpoint                          |
| Desactivar sin eliminar                 | —                                                                        | `status=disabled`                             |
| Visibilidad del estado                  | —                                                                        | `active` / `disabled` / `failing`             |
| Intercambio de configuración bloqueante | Sí ([`telephony.incoming` / `web.incoming`](/es/webhooks/call-incoming)) | Nunca: solo notificaciones                    |
| Ideal para                              | Configuración dinámica de llamadas                                       | Consumo de eventos en producción              |

Las integraciones nuevas deben consumir eventos mediante webhooks
basados en endpoints. Conserva (o agrega) una URL heredada solo si configuras llamadas
dinámicamente al momento de contestarlas o usas la ejecución de herramientas en modo webhook: esos
intercambios de solicitud/respuesta solo se ejecutan en la ruta heredada.

***

## Relacionado

<CardGroup cols={2}>
  <Card title="Catálogo de eventos" icon="list" href="/es/webhooks/events">
    Todos los tipos de eventos y sus cargas útiles.
  </Card>

  <Card title="Endpoints de webhook" icon="bolt" href="/es/webhooks/endpoints">
    Administra múltiples endpoints, filtros de eventos y secretos.
  </Card>

  <Card title="telephony.incoming / web.incoming" icon="phone" href="/es/webhooks/call-incoming">
    La solicitud bloqueante que tu servidor debe responder para configurar llamadas.
  </Card>

  <Card title="telephony.complete / web.complete" icon="phone" href="/es/webhooks/call-complete">
    Carga útil posterior a la llamada con transcripción, grabación y métricas.
  </Card>
</CardGroup>
