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

# Descripción general de Webhooks

> Recibe actualizaciones en tiempo real de Altur directamente en tu backend o CRM.

## Introducción a los Webhooks

Los webhooks te permiten recibir notificaciones en tiempo real sobre eventos en tu organización de Altur. Cuando ocurre un evento (por ejemplo, el fin de una llamada), Altur envía una solicitud HTTP POST con los datos del evento al endpoint que configures, manteniendo tus sistemas o CRM al día sin necesidad de hacer polling.

## Cómo Funcionan los Webhooks

1. **Configura tu Endpoint**\
   Configura la URL del webhook en la plataforma de Altur. Esta URL recibirá las solicitudes POST cuando se dispare un evento.

2. **Recibe Notificaciones**\
   Cuando ocurre un evento, Altur envía un POST con los detalles del evento en formato JSON a tu endpoint.

3. **Confirma la Recepción**\
   Tu endpoint debe responder con un código `200 OK` para confirmar la recepción. Altur espera hasta 5 segundos por la respuesta. Si la solicitud falla, hace timeout o devuelve un estado distinto de 2xx, Altur reintenta con backoff exponencial (hasta 5 intentos en total).

## Tipos de Evento

Los webhooks pueden disparar los siguientes tipos de evento:

* [`on_call_end`](https://docs.altur.io/api-reference/webhooks/on-call-end): Se dispara al final de una llamada. Envía información detallada de la llamada junto con datos básicos del agente de IA y del usuario final.
* [`campaign.status_changed`](https://docs.altur.io/es/api-reference/webhooks/campaign-status-changed): Se dispara cuando una Campaña cambia de estado. Incluye el snapshot de analíticas cuando la campaña llega a `finished`.
* [`campaign.cycle_completed`](https://docs.altur.io/es/api-reference/webhooks/campaign-cycle-completed): Se dispara cuando una iteración del ciclo de una Campaña termina, con el número de iteración completada y el timestamp de la siguiente iteración.

<Note>
  Los webhooks de campaña usan un envelope versionado (`event_id`, `event_type`,
  `occurred_at`, `api_version`, `project_id`, `data`). Usa `event_id` para
  procesamiento idempotente. El evento `on_call_end` es anterior a este
  envelope y mantiene su forma plana en el nivel superior.
</Note>

## Asegurando tus Webhooks

Para garantizar comunicaciones seguras y verificar la autenticidad de las solicitudes, Altur incluye una firma HMAC en el header `X-Altur-Signature` de cada solicitud.

### Cómo Funciona

* **Secreto Compartido**: Cada integración de webhook se configura con una llave secreta única codificada en base64.
* **Generación del HMAC**: Altur genera un hash HMAC SHA-256 usando el secreto compartido (decodificado de base64) y el payload JSON de la solicitud (serializado sin espacios). El hash se codifica en base64 y se incluye en el header `X-Altur-Signature`.
* **Validación**: Tu endpoint debe validar la firma usando el mismo secreto compartido y el mismo formato de serialización JSON.

### Función de Validación de Ejemplo

<CodeGroup>
  ```python Python theme={null}
  import json
  import base64
  import hmac
  import hashlib

  def is_valid_signature(secret: str, payload: Union[str, dict], signature: str) -> bool:
      """
      Valida la firma de un webhook de Altur.

      Args:
          secret: Llave secreta compartida codificada en base64
          payload: Payload de la solicitud (dict o string JSON)
          signature: Firma codificada en base64 del header X-Altur-Signature
      """
      # Convertir el payload a un formato JSON consistente (compacto, sin espacios)
      if isinstance(payload, dict):
          payload_bytes = json.dumps(payload, separators=(',', ':')).encode('utf-8')
      else:
          payload_bytes = payload.encode('utf-8')

      # Decodificar el secreto base64
      secret_bytes = base64.b64decode(secret)

      # Generar la firma esperada
      expected_signature = base64.b64encode(
          hmac.new(secret_bytes, payload_bytes, hashlib.sha256).digest()
      ).decode()

      return hmac.compare_digest(signature, expected_signature)
  ```

  ```javascript JavaScript theme={null}
  const crypto = require("crypto");

  function isValidSignature(secret, payload, signature) {
    /**
     * Valida la firma de un webhook de Altur.
     *
     * @param {string} secret - Llave secreta compartida codificada en base64
     * @param {object|string} payload - Payload de la solicitud (objeto o string JSON)
     * @param {string} signature - Firma codificada en base64 del header X-Altur-Signature
     */

    // Convertir el payload a un formato JSON consistente (compacto, sin espacios)
    const payloadString =
      typeof payload === "string" ? payload : JSON.stringify(payload);

    // Decodificar el secreto base64
    const secretBuffer = Buffer.from(secret, "base64");

    // Generar la firma esperada
    const expectedSignature = crypto
      .createHmac("sha256", secretBuffer)
      .update(payloadString)
      .digest("base64");

    // Comparar firmas con comparación timing-safe
    return crypto.timingSafeEqual(
      Buffer.from(signature, "base64"),
      Buffer.from(expectedSignature, "base64")
    );
  }

  // Ejemplo de uso
  const signature = req.headers["x-altur-signature"];
  const isValid = isValidSignature(yourSecret, req.body, signature);
  ```

  ```php PHP theme={null}
  function isValidSignature($secret, $payload, $signature) {
      /*
       * Valida la firma de un webhook de Altur.
       *
       * @param string $secret Llave secreta compartida codificada en base64
       * @param array|string $payload Payload de la solicitud (array o string JSON)
       * @param string $signature Firma codificada en base64 del header X-Altur-Signature
       */

      // Convertir el payload a un formato JSON consistente (compacto, sin espacios)
      $payloadString = is_string($payload)
          ? $payload
          : json_encode($payload, JSON_UNESCAPED_SLASHES);

      // Decodificar el secreto base64
      $secretBytes = base64_decode($secret);

      // Generar la firma esperada
      $expectedSignature = base64_encode(
          hash_hmac('sha256', $payloadString, $secretBytes, true)
      );

      // Comparar firmas con comparación timing-safe
      return hash_equals($signature, $expectedSignature);
  }

  // Ejemplo de uso
  $signature = $_SERVER['HTTP_X_ALTUR_SIGNATURE'];
  $isValid = isValidSignature($yourSecret, json_decode(file_get_contents('php://input'), true), $signature);
  ```
</CodeGroup>

### Ejemplo de Uso

```python Ejemplo 1: Usando un dict JSON ya parseado (recomendado) theme={null}
import json
request_body = request.get_json()  # JSON parseado por tu framework web
signature = request.headers.get('X-Altur-Signature')
shared_secret = "tu-secreto-codificado-en-base64"

if is_valid_signature(shared_secret, request_body, signature):
    print("Webhook válido!")
else:
    print("Webhook inválido!")
```

```python Ejemplo 2: Usando un string JSON crudo theme={null}
payload = '{"type":"on_call_end","status":"ended"}' # Payload crudo de la solicitud (JSON compacto)
signature = "firma-del-header" # Header X-Altur-Signature
shared_secret = "tu-secreto-codificado-en-base64" # Secreto compartido en base64

if is_valid_signature(shared_secret, payload, signature):
    print("Webhook válido!")
else:
    print("Webhook inválido!")
```

### Headers Enviados con el Webhook

* `Content-Type`: `application/json`
* `X-Altur-Signature`: hash HMAC SHA-256 codificado en base64 del payload JSON compacto

### Por Qué Importa

Este mecanismo garantiza que:

* La solicitud proviene de Altur.
* El payload no fue modificado en tránsito.

## Política de Reintentos

Si tu endpoint no responde con un estado 2xx (o no responde antes de los 5 segundos del request timeout), Altur reintenta la entrega:

* **Intentos máximos**: 5 (entrega inicial más 4 reintentos).
* **Backoff**: 60s después del primer fallo, duplicándose en cada intento (60s, 2m, 4m, 8m).

Si todos los intentos fallan, el evento se marca como fallido y no vuelve a entregarse.

## Ejemplo de Flujo

Un flujo típico para manejar webhooks:

1. **Configura tu Endpoint**

   Crea un endpoint en tu backend, por ejemplo `/webhooks/altur/on-call-end`. Debe ser accesible públicamente y aceptar solicitudes POST con `Content-Type: application/json`.

2. **Parsea el Payload**

   Extrae y procesa el payload JSON enviado por Altur. Maneja casos como payloads malformados o campos faltantes para evitar errores en runtime.

3. **Valida la Autenticidad**

   Usa el header `X-Altur-Signature` para verificar la autenticidad. Esto implica:

   * Recomputar la firma HMAC usando el secreto compartido y el payload.
   * Compararla con la firma del header.

4. **Responde Rápido**

   Devuelve `200 OK` para confirmar la recepción. Altur hace timeout a los 5 segundos, así que responde mucho antes de ese límite para evitar reintentos innecesarios.

5. **Registra los Eventos**

   Guarda logs de las solicitudes y del procesamiento para tener trazabilidad. Es especialmente útil al depurar problemas con payloads o reintentos.

6. **Maneja los Reintentos**

   Diseña tu sistema para tolerar reintentos. Asegúrate de que tu endpoint sea idempotente: procesar el mismo evento varias veces no debe duplicar acciones (por ejemplo, inserts en BD o llamadas a APIs).

## Buenas Prácticas

1. **Asegura tu Endpoint**

   * **Autentica las Solicitudes**: Verifica la firma HMAC en cada request. Usa un secreto único y seguro por integración.
   * **Restringe el Acceso**: Usa whitelisting de IP o firewall para permitir solicitudes únicamente desde los servidores de Altur.
   * **Cifra la Comunicación**: Usa HTTPS para que los datos viajen cifrados.

2. **Registra los Eventos**

   * **Registra Todo**: Guarda timestamps, headers, payloads y respuestas de cada solicitud entrante.
   * **Monitorea Fallos**: Configura alertas para fallos repetidos o solicitudes inválidas.
   * **Mantén Logs**: Retén los logs por un periodo razonable para auditoría o debugging.

3. **Prueba Bien**

   * **Simula Eventos**: Usa el dashboard de Altur para simular eventos y confirmar que tu endpoint los maneja correctamente.
   * **Prueba Casos Borde**: Payloads malformados, payloads grandes, campos faltantes.
   * **Usa Entornos de Staging**: Mantén un endpoint dedicado para pruebas, separado de producción.

4. **Optimiza el Rendimiento**

   * **Responde Rápido**: Altur hace timeout a los 5 segundos. Si el procesamiento es más largo, responde `200 OK` de inmediato y procesa en background.
   * **Minimiza el Procesamiento**: Haz solo validación ligera y encolado en el endpoint; deja el trabajo pesado a workers.
   * **Usa Caching**: Cuando aplique, cachea respuestas para solicitudes redundantes.

5. **Sé Idempotente**

   * **Evita Acciones Duplicadas**: Diseña el sistema para que los reintentos no causen operaciones duplicadas (inserts en BD, llamadas a APIs). Usa el `event_id` único de Altur para deduplicar.

6. **Comunica los Fallos**
   * **Devuelve Status Codes Correctos**: Usa `400` para solicitudes inválidas, `500` para errores del servidor, etc.
   * **Loguea y Notifica**: Registra los fallos y considera notificar al equipo si los reintentos críticos se agotan.

## Solución de Problemas con la Validación de Firma

Si tienes problemas validando la firma, revisa estos puntos comunes:

### 1. Formato de Serialización JSON

Usa el mismo formato JSON que Altur:

* **Formato compacto**: Sin espacios después de comas ni de dos puntos.
* **Orden consistente**: Usa exactamente el payload recibido.

```python theme={null}
# ✅ Correcto - JSON compacto
json.dumps(data, separators=(',', ':'))

# ❌ Incorrecto - incluye espacios
json.dumps(data)  # Resultado: {"key": "value"}
```

### 2. Formato de la Llave Secreta

* Tu secreto debe estar **codificado en base64**.
* Debe ser el mismo configurado en tu integración de webhook en Altur.

### 3. Nombre del Header

* El header es `X-Altur-Signature` (algunos frameworks son case-sensitive).
* Confirma que estás leyendo el header correcto.

### 4. Timing Attacks

Usa siempre funciones de comparación timing-safe:

* Python: `hmac.compare_digest()`
* Node.js: `crypto.timingSafeEqual()`
* PHP: `hash_equals()`

### 5. Probar tu Implementación

Puedes probar tu validación con este ejemplo:

```python theme={null}
# Datos de prueba
test_payload = {"test": "data"}
test_secret = "dGVzdC1zZWNyZXQ="  # base64 de "test-secret"
expected_signature = "ZjM2ZTc4YWJkZDQ1ZGZlYjM4NTIwYWY1ZmY1MzFkMTk4YmM0YzJmMzU0MTJjMmE3MGZjZGY4ZDhkOTYzOWY4OA=="

# Debe devolver True
result = is_valid_signature(test_secret, test_payload, expected_signature)
print(f"Resultado de validación: {result}")
```
