# 安全 (/zh/webhooks/security)

<!-- agent-signals: reading_time_min: 1 · est_tokens: 1017 · updated: 2026-07-30 -->
Related: [概览](/zh/webhooks/overview.md), [事件类型](/zh/webhooks/events.md), [测试](/zh/webhooks/testing.md)

通过检查每个 Webhook 请求的 HMAC-SHA256 签名，验证它确实来自 Firecrawl。这可以防止攻击者伪造 payload，并让你在处理数据之前确认其可信性。

<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();

      // 使用原始请求体解析器进行签名校验
      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 负载在传输过程中被加密。
