> ## 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

> Erhalte Echtzeit-Benachrichtigungen, wenn asynchrone Operationen abgeschlossen sind

<Check>
  **Wir erweitern aktiv die Unterstützung für Webhooks.**

  Gerade eingeführt: automatische Wiederholungen mit exponentiellem Backoff — fehlgeschlagene Lieferungen werden jetzt bis zu 5 Mal innerhalb von 30 Minuten erneut versucht.

  **Demnächst verfügbar:**

  * Teamweite Standard-Webhook-URLs
  * Kryptografische Signaturen zur Überprüfung der Nutzlast

  Möchtest du frühzeitig Zugang erhalten? Kontaktiere uns unter [info@olostep.com](mailto:info@olostep.com) oder tritt unserer [Slack-Community](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg) bei.
</Check>

## Übersicht

Webhooks liefern Echtzeit-HTTP-POST-Benachrichtigungen an deinen Server, wenn lang andauernde Operationen abgeschlossen sind. Anstatt den Status abzufragen, erhält deine Anwendung sofortige Updates.

### Anwendungsfälle

<CardGroup cols={2}>
  <Card title="Asynchrone Verarbeitung" icon="clock">
    Lass dich benachrichtigen, wenn Batches oder Crawls abgeschlossen sind, anstatt den Status abzufragen
  </Card>

  <Card title="Pipeline-Auslöser" icon="diagram-project">
    Automatisches Auslösen der nachgelagerten Verarbeitung, wenn Daten bereit sind
  </Card>

  <Card title="Benachrichtigungen" icon="bell">
    Sende Benachrichtigungen an Slack, E-Mail oder andere Systeme bei Abschluss
  </Card>

  <Card title="Datenabgleich" icon="arrows-rotate">
    Halte deine Datenbank mit den Olostep-Ergebnissen synchron
  </Card>
</CardGroup>

## Unterstützte Ereignisse

<AccordionGroup>
  <Accordion title="batch.completed" icon="layer-group">
    Wird ausgelöst, wenn ein Batch die Verarbeitung abgeschlossen hat (alle Elemente abgeschlossen oder fehlgeschlagen).

    ```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">
    Wird ausgelöst, wenn ein Crawl abgeschlossen ist und alle entdeckten Seiten verarbeitet wurden.

    ```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>

***

## Einrichten von Webhooks

Übergebe `webhook`, wenn du eine Ressource erstellst. Diese URL erhält die Abschlussbenachrichtigung.

<Note>
  **Parametername:** Der kanonische Parameter ist `webhook`. Zur Rückwärtskompatibilität wird auch `webhook_url` als Alias akzeptiert.
</Note>

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

  # Batch-Beispiel
  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"
      }
  )

  # Crawl-Beispiel
  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}
  // Batch-Beispiel
  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'
    })
  });

  // Crawl-Beispiel
  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}
  # Batch-Beispiel
  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"
    }'

  # Crawl-Beispiel
  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>

***

## Webhook-Nutzlast

Alle Webhook-Nutzlasten folgen einer einheitlichen Umschlagstruktur:

```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
  }
}
```

### Umschlagfelder

| Feld               | Beschreibung                                                        |
| ------------------ | ------------------------------------------------------------------- |
| `id`               | Ereignis-ID — **gleich bei allen Wiederholungsversuchen**           |
| `object`           | Ereignistyp (z.B. `event.batch.completed`)                          |
| `timestamp`        | Wann dieser Lieferungsversuch gesendet wurde (Epoch ms)             |
| `delivery_attempt` | Aktueller Versuch / maximale Versuche (z.B. `1/5`, `3/5`)           |
| `data`             | Die tatsächlichen Ressourcendaten (gleiches Format wie API-Antwort) |

<Tip>
  Verwende das `id`-Feld, um Webhook-Lieferungen in deinem Empfänger zu deduplizieren. Die gleiche Ereignis-ID erscheint in allen Wiederholungsversuchen.
</Tip>

***

## Wiederholungsverhalten

Fehlgeschlagene Webhook-Lieferungen werden automatisch mit exponentiellem Backoff über ein 30-minütiges Fenster erneut versucht:

| Versuch | Verzögerung vor dem Versuch | Kumulative Zeit |
| ------- | --------------------------- | --------------- |
| 1       | Sofort                      | 0 min           |
| 2       | \~2 min                     | \~2 min         |
| 3       | \~4 min                     | \~6 min         |
| 4       | \~7 min                     | \~13 min        |
| 5       | \~15 min                    | \~28 min        |

**Gesamtes Wiederholungsfenster:** 30 Minuten\
**Timeout pro Anfrage:** 30 Sekunden

### Was als Erfolg zählt

Dein Endpunkt muss innerhalb von 30 Sekunden einen `2xx`-Statuscode zurückgeben. Jede andere Antwort löst einen erneuten Versuch aus.

| Antwort              | Ergebnis                                               |
| -------------------- | ------------------------------------------------------ |
| `200 OK`             | ✅ Geliefert                                            |
| `201 Created`        | ✅ Geliefert                                            |
| `301 Redirect`       | ❌ Erneut versuchen (wir folgen keinen Weiterleitungen) |
| `400 Bad Request`    | ❌ Erneut versuchen                                     |
| `500 Server Error`   | ❌ Erneut versuchen                                     |
| Timeout (>30s)       | ❌ Erneut versuchen                                     |
| Verbindung abgelehnt | ❌ Erneut versuchen                                     |

***

## Best Practices

<AccordionGroup>
  <Accordion title="Schnell antworten, asynchron verarbeiten">
    Gib sofort `200 OK` zurück und verarbeite den Webhook asynchron. Wenn deine Verarbeitung länger als 30 Sekunden dauert, versuchen wir es erneut — was zu doppelten Lieferungen führt.

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

    webhook_queue = Queue()

    @app.route('/webhooks/olostep', methods=['POST'])
    def handle_webhook():
        # In die Warteschlange für asynchrone Verarbeitung stellen
        webhook_queue.put(request.json)
        
        # Sofort zurückgeben
        return 'OK', 200

    def process_webhooks():
        while True:
            event = webhook_queue.get()
            # Langsame Verarbeitung erfolgt hier
            process_event(event)

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

  <Accordion title="Idempotente Handler implementieren">
    Verwende das `id`-Feld zur Deduplizierung. Speichere verarbeitete Ereignis-IDs und überspringe Duplikate.

    ```python theme={null}
    processed_events = set()  # Verwende Redis/DB in der Produktion

    def handle_event(event):
        if event['id'] in processed_events:
            return  # Bereits verarbeitet
        
        # Ereignis verarbeiten
        process_batch_completed(event['data'])
        
        # Als verarbeitet markieren
        processed_events.add(event['id'])
    ```
  </Accordion>

  <Accordion title="Webhook-Empfänge protokollieren">
    Protokolliere alle Webhook-Empfänge zur Fehlersuche. Füge die Ereignis-ID, den Zeitstempel und das Verarbeitungsergebnis ein.

    ```python theme={null}
    import logging

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

  <Accordion title="HTTPS-Endpunkte verwenden">
    Verwende immer HTTPS für Webhook-Endpunkte. HTTP-Endpunkte sind anfällig für Abhören und Man-in-the-Middle-Angriffe.
  </Accordion>
</AccordionGroup>

***

## Fehlerbehebung

<AccordionGroup>
  <Accordion title="Keine Webhooks empfangen">
    1. Überprüfe, ob der `webhook`-Parameter in deiner Anfrage enthalten war
    2. Überprüfe, ob dein Endpunkt öffentlich zugänglich ist (nicht localhost)
    3. Überprüfe die Serverprotokolle auf eingehende Anfragen
    4. Stelle sicher, dass du einen `2xx`-Statuscode zurückgibst
  </Accordion>

  <Accordion title="Doppelte Webhooks empfangen">
    Dies ist bei Wiederholungen zu erwarten. Implementiere eine idempotente Verarbeitung mit dem `id`-Feld:

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

  <Accordion title="Webhooks laufen ab">
    Dein Endpunkt muss innerhalb von 30 Sekunden antworten. Verarbeite Webhooks asynchron:

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

***

## Demnächst verfügbar

<CardGroup cols={2}>
  <Card title="Team-Standard-URL" icon="gear">
    Konfiguriere eine Standard-Webhook-URL in deinen Kontoeinstellungen. Alle Anfragen verwenden diese URL, es sei denn, sie wird überschrieben.
  </Card>

  <Card title="Signaturüberprüfung" icon="shield-check">
    Kryptografische Signaturen (HMAC-SHA256) zur Überprüfung, dass Webhook-Nutzlasten von Olostep stammen.
  </Card>
</CardGroup>

<Note>
  Möchtest du frühzeitig Zugang zu diesen Funktionen erhalten? Kontaktiere uns unter [info@olostep.com](mailto:info@olostep.com) oder tritt unserer [Slack-Community](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg) bei.
</Note>
