# セキュリティ (/ja/webhooks/security)

<!-- agent-signals: reading_time_min: 1 · est_tokens: 1090 · updated: 2026-07-30 -->
Related: [概要](/ja/webhooks/overview.md), [イベントタイプ](/ja/webhooks/events.md), [テスト](/ja/webhooks/testing.md)

HMAC-SHA256 署名を確認して、各 webhook リクエストが実際に Firecrawl から送信されたものかを検証します。これにより、攻撃者によるペイロードの偽装を防ぎ、処理を実行する前にそのデータを信頼できるようになります。

<div id="secret-key">
  ## シークレットキー [#シークレットキー]
</div>

webhook シークレットは、アカウント設定の[Advanced タブ](https://www.firecrawl.dev/app/settings?tab=advanced)で確認できます。各アカウントには、すべての webhook リクエストに署名するために使用される固有のシークレットが割り当てられています。

<Warning>
  webhook シークレットは厳重に管理し、決して公開しないでください。漏えいした可能性がある場合は、直ちにアカウント設定から再生成してください。
</Warning>

<div id="signature-verification">
  ## 署名の検証 [#署名の検証]
</div>

各 webhook リクエストには、`X-Firecrawl-Signature` ヘッダーが含まれます：

```
X-Firecrawl-Signature: sha256=abc123def456...
```

<div id="how-to-verify">
  ### 検証方法 [#検証方法]
</div>

1. `X-Firecrawl-Signature` ヘッダーから署名を取り出す
2. パース前の生のリクエストボディを取得する
3. シークレットキーを使って HMAC-SHA256 を計算する
4. タイミングセーフな比較関数を使って署名を比較する

<div id="implementation">
  ### 実装 [#実装]
</div>

<CodeGroup>
  <CodeBlockTabs defaultValue="Node／Express" groupId="node-express+python-flask">
    <CodeBlockTabsList>
      <CodeBlockTabsTrigger value="Node／Express">
        Node／Express
      </CodeBlockTabsTrigger>

      <CodeBlockTabsTrigger value="Python/Flask">
        Python/Flask
      </CodeBlockTabsTrigger>
    </CodeBlockTabsList>

    <CodeBlockTab value="Node／Express">
      ```js  
      import crypto from 'crypto';
      import express from 'express';

      const app = express();

      // 署名検証のために raw 本文パーサーを使用
      app.use('/webhook/firecrawl', express.raw({ type: 'application/json' }));

      app.post('/webhook/firecrawl', (req, res) => {
        const signature = req.get('X-Firecrawl-Signature');
        const webhookSecret = process.env.FIRECRAWL_WEBHOOK_SECRET;
        
        if (!signature || !webhookSecret) {
          return res.status(401).send('認証されていません');
        }
        
        // 署名ヘッダーからハッシュを抽出
        const [algorithm, hash] = signature.split('=');
        if (algorithm !== 'sha256') {
          return res.status(401).send('無効な署名アルゴリズム');
        }
        
        // 期待される署名を算出
        const expectedSignature = crypto
          .createHmac('sha256', webhookSecret)
          .update(req.body)
          .digest('hex');
        
        // タイミング安全な比較で署名を検証
        if (!crypto.timingSafeEqual(Buffer.from(hash, 'hex'), Buffer.from(expectedSignature, 'hex'))) {
          return res.status(401).send('無効な署名');
        }
        
        // 検証済みの webhook を解析して処理
        const event = JSON.parse(req.body);
        console.log('検証済みの Firecrawl webhook:', event);
        
        res.status(200).send('ok');
      });

      app.listen(3000, () => console.log('ポート 3000 で待機中'));
      ```
    </CodeBlockTab>

    <CodeBlockTab value="Python/Flask">
      ```python  
      import hmac
      import hashlib
      from flask import Flask, request, abort

      app = Flask(__name__)

      WEBHOOK_SECRET = 'your-webhook-secret-here'  # Firecrawl ダッシュボードで取得

      @app.post('/webhook/firecrawl')
      def webhook():
          signature = request.headers.get('X-Firecrawl-Signature')
          
          if not signature:
              abort(401, '署名ヘッダーが見つかりません')
          
          # 署名ヘッダーからハッシュを取り出す
          try:
              algorithm, hash_value = signature.split('=', 1)
              if algorithm != 'sha256':
                  abort(401, '署名アルゴリズムが不正です')
          except ValueError:
              abort(401, '署名フォーマットが不正です')
          
          # 期待される署名を算出
          expected_signature = hmac.new(
              WEBHOOK_SECRET.encode('utf-8'),
              request.data,
              hashlib.sha256
          ).hexdigest()
          
          # タイミングセーフな比較で署名を検証する
          if not hmac.compare_digest(hash_value, expected_signature):
              abort(401, '署名が不正です')
          
          # 検証済みの Webhook を解析・処理
          event = request.get_json(force=True)
          print('検証済みの Firecrawl Webhook:', event)
          
          return 'ok', 200

      if __name__ == '__main__':
          app.run(port=3000)
      ```
    </CodeBlockTab>
  </CodeBlockTabs>
</CodeGroup>

<div id="best-practices">
  ## ベストプラクティス [#ベストプラクティス]
</div>

* **すべてのリクエストを確認してください。** webhook のペイロードを処理する前に、必ず署名を確認してください。確認に失敗したリクエストは、`401` ステータスで拒否してください。
* **タイミングセーフな比較を使う。** 標準的な文字列比較はタイミング情報を漏えいするおそれがあります。Node.js では `crypto.timingSafeEqual()` を、Python では `hmac.compare_digest()` を使ってください。
* **エンドポイントは HTTPS 経由で公開してください。** これにより、webhook のペイロードは転送中に暗号化されます。
