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

# Webhooks

> Recibe notificaciones en tiempo real cuando las operaciones asíncronas se completan

<Check>
  **Estamos ampliando activamente el soporte para webhooks.**

  Recién llegado: reintentos automáticos con retroceso exponencial — las entregas fallidas ahora se reintentan hasta 5 veces en 30 minutos.

  **Próximamente:**

  * URLs de webhook predeterminadas para todo el equipo
  * Firmas criptográficas para la verificación de cargas útiles

  ¿Quieres acceso anticipado? Contáctanos en [info@olostep.com](mailto:info@olostep.com) o únete a nuestra [comunidad de Slack](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg).
</Check>

## Descripción general

Los webhooks entregan notificaciones HTTP POST en tiempo real a tu servidor cuando las operaciones de larga duración se completan. En lugar de sondear para obtener el estado, tu aplicación recibe actualizaciones instantáneas.

### Casos de uso

<CardGroup cols={2}>
  <Card title="Procesamiento Asíncrono" icon="clock">
    Recibe notificaciones cuando los lotes o rastreos se completan en lugar de sondear
  </Card>

  <Card title="Disparadores de Pipeline" icon="diagram-project">
    Activa automáticamente el procesamiento posterior cuando los datos estén listos
  </Card>

  <Card title="Alertas" icon="bell">
    Envía alertas a Slack, email u otros sistemas al completarse
  </Card>

  <Card title="Sincronización de Datos" icon="arrows-rotate">
    Mantén tu base de datos sincronizada con los resultados de Olostep
  </Card>
</CardGroup>

## Eventos Soportados

<AccordionGroup>
  <Accordion title="batch.completed" icon="layer-group">
    Se activa cuando un lote termina de procesarse (todos los elementos completados o fallidos).

    ```json theme={null}
    {
      "id": "event_a1b2c3d4e5f6g7h8",
      "object": "event.batch.completed",
      "timestamp": 1737570000000,
      "delivery_attempt": "1/5",
      "data": {
        "id": "batch_xyz123",
        "object": "batch",
        "status": "completed",
        "items_total": 100,
        "items_completed": 98,
        "items_failed": 2,
        "created_at": "2024-01-15T10:00:00Z",
        "completed_at": "2024-01-15T10:05:32Z"
      }
    }
    ```
  </Accordion>

  <Accordion title="crawl.completed" icon="spider-web">
    Se activa cuando un rastreo termina y todas las páginas descubiertas han sido procesadas.

    ```json theme={null}
    {
      "id": "event_x9y8z7w6v5u4t3s2",
      "object": "event.crawl.completed",
      "timestamp": 1737570000000,
      "delivery_attempt": "1/5",
      "data": {
        "id": "crawl_abc789",
        "object": "crawl",
        "status": "completed",
        "start_url": "https://example.com",
        "urls_count": 87,
        "max_pages": 100,
        "max_depth": 3,
        "actual_max_depth": 3,
        "start_epoch": 1737569500000,
        "start_date": "2024-01-15"
      }
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Configuración de Webhooks

Pasa `webhook` al crear un recurso. Esta URL recibe la notificación de finalización.

<Note>
  **Nombre del parámetro:** El parámetro canónico es `webhook`. Para compatibilidad con versiones anteriores, `webhook_url` también se acepta como un alias.
</Note>

<CodeGroup>
  ```python Python theme={null}
  import requests

  # Ejemplo de lote
  response = requests.post(
      "https://api.olostep.com/v1/batches",
      headers={"Authorization": "Bearer <YOUR_API_KEY>"},
      json={
          "items": [
              {"url": "https://example.com/page1", "custom_id": "1"},
              {"url": "https://example.com/page2", "custom_id": "2"}
          ],
          "webhook": "https://your-server.com/webhooks/olostep"
      }
  )

  # Ejemplo de rastreo
  response = requests.post(
      "https://api.olostep.com/v1/crawls",
      headers={"Authorization": "Bearer <YOUR_API_KEY>"},
      json={
          "start_url": "https://example.com",
          "max_pages": 50,
          "webhook": "https://your-server.com/webhooks/olostep"
      }
  )
  ```

  ```js Node theme={null}
  // Ejemplo de lote
  const batchResponse = await fetch('https://api.olostep.com/v1/batches', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer <YOUR_API_KEY>',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      items: [
        { url: 'https://example.com/page1', custom_id: "1" },
        { url: 'https://example.com/page2', custom_id: "2" }
      ],
      webhook: 'https://your-server.com/webhooks/olostep'
    })
  });

  // Ejemplo de rastreo
  const crawlResponse = await fetch('https://api.olostep.com/v1/crawls', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer <YOUR_API_KEY>',
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      start_url: 'https://example.com',
      max_pages: 50,
      webhook: 'https://your-server.com/webhooks/olostep'
    })
  });
  ```

  ```bash cURL theme={null}
  # Ejemplo de lote
  curl -X POST "https://api.olostep.com/v1/batches" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "items": [
        {"url": "https://example.com/page1", "custom_id": "1"},
        {"url": "https://example.com/page2", "custom_id": "2"}
      ],
      "webhook": "https://your-server.com/webhooks/olostep"
    }'

  # Ejemplo de rastreo
  curl -X POST "https://api.olostep.com/v1/crawls" \
    -H "Authorization: Bearer $OLOSTEP_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "start_url": "https://example.com",
      "max_pages": 50,
      "webhook": "https://your-server.com/webhooks/olostep"
    }'
  ```
</CodeGroup>

***

## Carga Útil del Webhook

Todas las cargas útiles de webhook siguen una estructura de sobre unificada:

```json theme={null}
{
  "id": "event_a1b2c3d4e5f6g7h8",
  "object": "event.batch.completed",
  "timestamp": 1737570000000,
  "delivery_attempt": "1/5",
  "data": {
    "id": "batch_xyz123",
    "object": "batch",
    "status": "completed",
    "items_total": 100,
    "items_completed": 98,
    "items_failed": 2
  }
}
```

### Campos del Sobre

| Campo              | Descripción                                                             |
| ------------------ | ----------------------------------------------------------------------- |
| `id`               | ID del evento — **igual en todos los intentos de reintento**            |
| `object`           | Tipo de evento (e.g., `event.batch.completed`)                          |
| `timestamp`        | Cuándo se envió este intento de entrega (epoch ms)                      |
| `delivery_attempt` | Intento actual / intentos máximos (e.g., `1/5`, `3/5`)                  |
| `data`             | Los datos reales del recurso (mismo formato que la respuesta de la API) |

<Tip>
  Usa el campo `id` para eliminar duplicados en las entregas de webhooks en tu receptor. El mismo ID de evento aparece en todos los intentos de reintento.
</Tip>

***

## Comportamiento de Reintento

Las entregas fallidas de webhooks se reintentan automáticamente con retroceso exponencial durante una ventana de 30 minutos:

| Intento | Retraso antes del intento | Tiempo acumulado |
| ------- | ------------------------- | ---------------- |
| 1       | Inmediato                 | 0 min            |
| 2       | \~2 min                   | \~2 min          |
| 3       | \~4 min                   | \~6 min          |
| 4       | \~7 min                   | \~13 min         |
| 5       | \~15 min                  | \~28 min         |

**Ventana total de reintento:** 30 minutos\
**Tiempo de espera por solicitud:** 30 segundos

### Qué Cuenta como Éxito

Tu endpoint debe devolver un código de estado `2xx` dentro de 30 segundos. Cualquier otra respuesta desencadena un reintento.

| Respuesta               | Resultado                               |
| ----------------------- | --------------------------------------- |
| `200 OK`                | ✅ Entregado                             |
| `201 Created`           | ✅ Entregado                             |
| `301 Redirect`          | ❌ Reintento (no seguimos redirecciones) |
| `400 Bad Request`       | ❌ Reintento                             |
| `500 Server Error`      | ❌ Reintento                             |
| Tiempo de espera (>30s) | ❌ Reintento                             |
| Conexión rechazada      | ❌ Reintento                             |

***

## Mejores Prácticas

<AccordionGroup>
  <Accordion title="Responde rápidamente, procesa de forma asíncrona">
    Devuelve `200 OK` inmediatamente y procesa el webhook de forma asíncrona. Si tu procesamiento tarda más de 30 segundos, reintentaremos — causando entregas duplicadas.

    ```python theme={null}
    from queue import Queue
    import threading

    webhook_queue = Queue()

    @app.route('/webhooks/olostep', methods=['POST'])
    def handle_webhook():
        # Cola para procesamiento asíncrono
        webhook_queue.put(request.json)
        
        # Devuelve inmediatamente
        return 'OK', 200

    def process_webhooks():
        while True:
            event = webhook_queue.get()
            # El procesamiento lento ocurre aquí
            process_event(event)

    threading.Thread(target=process_webhooks, daemon=True).start()
    ```
  </Accordion>

  <Accordion title="Implementa manejadores idempotentes">
    Usa el campo `id` para eliminar duplicados. Almacena los IDs de eventos procesados y omite duplicados.

    ```python theme={null}
    processed_events = set()  # Usa Redis/DB en producción

    def handle_event(event):
        if event['id'] in processed_events:
            return  # Ya procesado
        
        # Procesa el evento
        process_batch_completed(event['data'])
        
        # Marca como procesado
        processed_events.add(event['id'])
    ```
  </Accordion>

  <Accordion title="Registra los recibos de webhooks">
    Registra todos los recibos de webhooks para depuración. Incluye el ID del evento, la marca de tiempo y el resultado del procesamiento.

    ```python theme={null}
    import logging

    @app.route('/webhooks/olostep', methods=['POST'])
    def handle_webhook():
        event = request.json
        logging.info(f"Webhook recibido: id={event['id']} tipo={event['object']} intento={event['delivery_attempt']}")
        
        try:
            process_event(event)
            logging.info(f"Webhook procesado: id={event['id']}")
        except Exception as e:
            logging.error(f"Webhook fallido: id={event['id']} error={e}")
            raise
        
        return 'OK', 200
    ```
  </Accordion>

  <Accordion title="Usa endpoints HTTPS">
    Siempre usa HTTPS para los endpoints de webhooks. Los endpoints HTTP son vulnerables a escuchas y ataques de intermediario.
  </Accordion>
</AccordionGroup>

***

## Solución de Problemas

<AccordionGroup>
  <Accordion title="No se reciben webhooks">
    1. Verifica que el parámetro `webhook` se incluyó en tu solicitud
    2. Verifica que tu endpoint sea accesible públicamente (no localhost)
    3. Revisa los registros de tu servidor para solicitudes entrantes
    4. Asegúrate de devolver un código de estado `2xx`
  </Accordion>

  <Accordion title="Recibiendo webhooks duplicados">
    Esto es esperado durante los reintentos. Implementa un manejo idempotente usando el campo `id`:

    ```python theme={null}
    def handle_event(event):
        if already_processed(event['id']):
            return  # Omitir duplicado
        
        process_event(event)
        mark_processed(event['id'])
    ```
  </Accordion>

  <Accordion title="Webhooks agotando el tiempo">
    Tu endpoint debe responder dentro de 30 segundos. Procesa los webhooks de forma asíncrona:

    ```python theme={null}
    @app.route('/webhooks', methods=['POST'])
    def webhook():
        queue.enqueue(process_webhook, request.json)
        return 'OK', 200  # Responde inmediatamente
    ```
  </Accordion>
</AccordionGroup>

***

## Próximamente

<CardGroup cols={2}>
  <Card title="URL Predeterminada del Equipo" icon="gear">
    Configura una URL de webhook predeterminada en la configuración de tu cuenta. Todas las solicitudes usarán esta URL a menos que se sobrescriba.
  </Card>

  <Card title="Verificación de Firmas" icon="shield-check">
    Firmas criptográficas (HMAC-SHA256) para verificar que las cargas útiles de webhook provienen de Olostep.
  </Card>
</CardGroup>

<Note>
  ¿Quieres acceso anticipado a estas funciones? Contáctanos en [info@olostep.com](mailto:info@olostep.com) o únete a nuestra [comunidad de Slack](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg).
</Note>
