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

> 非同期操作が完了したときにリアルタイム通知を受け取る

<Check>
  **Webhookサポートを積極的に拡大中です。**

  新機能: 指数バックオフを使用した自動再試行 — 失敗した配信は30分以内に最大5回再試行されます。

  **近日公開予定:**

  * チーム全体のデフォルトWebhook URL
  * ペイロード検証のための暗号署名

  早期アクセスをご希望ですか？ [info@olostep.com](mailto:info@olostep.com) までご連絡いただくか、[Slackコミュニティ](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg)に参加してください。
</Check>

## 概要

Webhooksは、長時間実行される操作が完了したときに、リアルタイムのHTTP POST通知をサーバーに送信します。ステータスをポーリングする代わりに、アプリケーションは即座に更新を受け取ります。

### 使用例

<CardGroup cols={2}>
  <Card title="非同期処理" icon="clock">
    バッチやクロールが完了したときに通知を受け取ることでポーリングを避ける
  </Card>

  <Card title="パイプライントリガー" icon="diagram-project">
    データが準備できたときに自動的に下流処理をトリガーする
  </Card>

  <Card title="アラート" icon="bell">
    完了時にSlack、メール、その他のシステムにアラートを送信する
  </Card>

  <Card title="データ同期" icon="arrows-rotate">
    Olostepの結果とデータベースを同期させる
  </Card>
</CardGroup>

## サポートされているイベント

<AccordionGroup>
  <Accordion title="batch.completed" icon="layer-group">
    バッチの処理が終了したときに発生します（すべてのアイテムが完了または失敗した場合）。

    ```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">
    クロールが終了し、発見されたすべてのページが処理されたときに発生します。

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

***

## Webhooksの設定

リソースを作成する際に`webhook`を渡します。このURLが完了通知を受け取ります。

<Note>
  **パラメータ名:** 標準のパラメータは`webhook`です。後方互換性のために、`webhook_url`もエイリアスとして受け入れられます。
</Note>

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

  # バッチの例
  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"
      }
  )

  # クロールの例
  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}
  // バッチの例
  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'
    })
  });

  // クロールの例
  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}
  # バッチの例
  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"
    }'

  # クロールの例
  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ペイロード

すべてのWebhookペイロードは統一されたエンベロープ構造に従います：

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

### エンベロープフィールド

| フィールド              | 説明                                 |
| ------------------ | ---------------------------------- |
| `id`               | イベントID — **すべての再試行で同じ**            |
| `object`           | イベントタイプ（例：`event.batch.completed`） |
| `timestamp`        | この配信試行が送信された時刻（エポックミリ秒）            |
| `delivery_attempt` | 現在の試行 / 最大試行回数（例：`1/5`, `3/5`）     |
| `data`             | 実際のリソースデータ（APIレスポンスと同じ形式）          |

<Tip>
  `id`フィールドを使用して、受信側でWebhook配信を重複排除します。同じイベントIDがすべての再試行に表示されます。
</Tip>

***

## 再試行の動作

失敗したWebhook配信は、30分間のウィンドウで指数バックオフを使用して自動的に再試行されます：

| 試行 | 次の試行までの遅延 | 累積時間 |
| -- | --------- | ---- |
| 1  | 即時        | 0分   |
| 2  | 約2分       | 約2分  |
| 3  | 約4分       | 約6分  |
| 4  | 約7分       | 約13分 |
| 5  | 約15分      | 約28分 |

**合計再試行ウィンドウ:** 30分\
**リクエストごとのタイムアウト:** 30秒

### 成功と見なされる条件

エンドポイントは30秒以内に`2xx`ステータスコードを返す必要があります。それ以外の応答は再試行をトリガーします。

| 応答                 | 結果                   |
| ------------------ | -------------------- |
| `200 OK`           | ✅ 配信済み               |
| `201 Created`      | ✅ 配信済み               |
| `301 Redirect`     | ❌ 再試行（リダイレクトは追跡しません） |
| `400 Bad Request`  | ❌ 再試行                |
| `500 Server Error` | ❌ 再試行                |
| タイムアウト（>30s）       | ❌ 再試行                |
| 接続拒否               | ❌ 再試行                |

***

## ベストプラクティス

<AccordionGroup>
  <Accordion title="迅速に応答し、非同期で処理する">
    `200 OK`を即座に返し、Webhookを非同期で処理します。処理が30秒以上かかる場合、再試行が行われ、重複配信が発生します。

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

    webhook_queue = Queue()

    @app.route('/webhooks/olostep', methods=['POST'])
    def handle_webhook():
        # 非同期処理のためのキュー
        webhook_queue.put(request.json)
        
        # 即座に返す
        return 'OK', 200

    def process_webhooks():
        while True:
            event = webhook_queue.get()
            # ここで遅い処理が行われます
            process_event(event)

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

  <Accordion title="冪等性のあるハンドラーを実装する">
    `id`フィールドを使用して重複を排除します。処理済みのイベントIDを保存し、重複をスキップします。

    ```python theme={null}
    processed_events = set()  # 本番環境ではRedis/DBを使用

    def handle_event(event):
        if event['id'] in processed_events:
            return  # すでに処理済み
        
        # イベントを処理
        process_batch_completed(event['data'])
        
        # 処理済みとしてマーク
        processed_events.add(event['id'])
    ```
  </Accordion>

  <Accordion title="Webhookの受信をログに記録する">
    デバッグのためにすべてのWebhook受信をログに記録します。イベントID、タイムスタンプ、処理結果を含めます。

    ```python theme={null}
    import logging

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

  <Accordion title="HTTPSエンドポイントを使用する">
    Webhookエンドポイントには常にHTTPSを使用してください。HTTPエンドポイントは盗聴や中間者攻撃に対して脆弱です。
  </Accordion>
</AccordionGroup>

***

## トラブルシューティング

<AccordionGroup>
  <Accordion title="Webhookを受信していない">
    1. リクエストに`webhook`パラメータが含まれていることを確認する
    2. エンドポイントが公開アクセス可能であることを確認する（localhostではない）
    3. サーバーログで受信リクエストを確認する
    4. `2xx`ステータスコードを返していることを確認する
  </Accordion>

  <Accordion title="重複したWebhookを受信している">
    これは再試行中に予想される動作です。`id`フィールドを使用して冪等性のある処理を実装してください：

    ```python theme={null}
    def handle_event(event):
        if already_processed(event['id']):
            return  # 重複をスキップ
        
        process_event(event)
        mark_processed(event['id'])
    ```
  </Accordion>

  <Accordion title="Webhookがタイムアウトしている">
    エンドポイントは30秒以内に応答する必要があります。Webhookを非同期で処理してください：

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

***

## 近日公開予定

<CardGroup cols={2}>
  <Card title="チームデフォルトURL" icon="gear">
    アカウント設定でデフォルトのWebhook URLを設定します。すべてのリクエストはこのURLを使用しますが、上書きすることも可能です。
  </Card>

  <Card title="署名検証" icon="shield-check">
    OlostepからのWebhookペイロードを検証するための暗号署名（HMAC-SHA256）。
  </Card>
</CardGroup>

<Note>
  これらの機能への早期アクセスをご希望ですか？ [info@olostep.com](mailto:info@olostep.com) までご連絡いただくか、[Slackコミュニティ](https://olostep-users.slack.com/join/shared_invite/zt-2bfddyi8h-JzfjOgavg~98DJ1om1B5Lg)に参加してください。
</Note>
