> ## Documentation Index
> Fetch the complete documentation index at: https://student-213fb9fc.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# 安全

> 验证 Webhook 的真实性

Firecrawl 使用 HMAC-SHA256 对每个 Webhook 请求进行签名。验证签名可以确保请求是可信的，且未被篡改。

<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>
  ```js Node/Express theme={null}
  import crypto from 'crypto';
  import express from 'express';

  const app = express();

  // 使用原始请求体解析器进行签名校验
  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'));
  ```

  ```python Python/Flask theme={null}
  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)
  ```
</CodeGroup>

<div id="best-practices">
  ## 最佳实践
</div>

<div id="always-verify-signatures">
  ### 始终验证签名
</div>

切勿在未验证签名的情况下处理 webhook：

```javascript theme={null}
app.post('/webhook', (req, res) => {
  if (!verifySignature(req)) {
    return res.status(401).send('未授权');
  }
  processWebhook(req.body);
  res.status(200).send('OK');
});
```

<div id="use-timing-safe-comparisons">
  ### 使用时间常数/计时安全的比较
</div>

标准字符串比较可能泄露时间信息。在 Node.js 中使用 `crypto.timingSafeEqual()`，在 Python 中使用 `hmac.compare_digest()`。

<div id="use-https">
  ### 使用 HTTPS
</div>

始终为 webhook 端点使用 HTTPS，确保传输过程中的载荷已被加密。
