You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何验证SendGrid入站解析Webhook发送至服务器的POST请求?

验证SendGrid Inbound Parse Webhook请求的最佳方案

SendGrid官方提供了一套基于HMAC-SHA256的签名验证机制,这是验证请求合法性最可靠的方式,能有效防止伪造请求和重放攻击。下面是具体的实现步骤和注意事项:

一、先在SendGrid控制台开启签名验证

首先登录你的SendGrid账号,进入 Settings > Mail Settings > Inbound Parse,找到你配置的对应whatever@mydomain.com的解析规则,确保"Enable Signatures"选项处于开启状态。这一步是让SendGrid在发送POST请求时自动带上签名相关的HTTP头。

二、服务器端验证逻辑实现

SendGrid会在每个请求里带上两个关键HTTP头:

  • X-Sendgrid-Timestamp:请求发送的Unix时间戳(秒级)
  • X-Sendgrid-Signature:用你的API密钥生成的HMAC签名

你需要在服务器端完成以下三步验证:

1. 验证时间戳,防止重放攻击

攻击者可能会重复发送旧的合法请求,所以先检查时间戳和当前服务器时间的差值,一般建议限制在5分钟(300秒)以内,超出范围直接拒绝请求。

2. 生成待签名的字符串

把时间戳和请求的原始请求体拼接成 {timestamp}.{raw_request_body} 的格式——这里一定要用原始的请求体,不能是解析后的JSON对象,因为格式变化(比如空格、换行符)会导致签名不匹配。

3. 计算签名并对比

用你的SendGrid API密钥作为密钥,HMAC-SHA256算法对拼接后的字符串生成哈希值,再和请求头里的X-Sendgrid-Signature对比,一致则说明请求合法。

三、代码示例(常用语言)

Node.js/Express 示例

注意要先配置中间件获取原始请求体:

const express = require('express');
const crypto = require('crypto');
const app = express();

// 配置中间件,保留原始请求体
app.use(express.raw({ type: 'application/json' }));

const SENDGRID_API_KEY = process.env.SENDGRID_API_KEY; // 用环境变量存储密钥,不要硬编码

function validateSendgridRequest(req) {
  const timestamp = req.headers['x-sendgrid-timestamp'];
  const signature = req.headers['x-sendgrid-signature'];

  if (!timestamp || !signature) return false;

  // 验证时间戳
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) return false;

  // 生成待签名内容
  const signedPayload = `${timestamp}.${req.body.toString()}`;

  // 计算签名
  const computedSignature = crypto
    .createHmac('sha256', SENDGRID_API_KEY)
    .update(signedPayload)
    .digest('hex');

  // 安全对比签名(避免时序攻击)
  return crypto.timingSafeEqual(Buffer.from(computedSignature), Buffer.from(signature));
}

// 处理Inbound Parse请求的路由
app.post('/your-webhook-endpoint', (req, res) => {
  if (!validateSendgridRequest(req)) {
    return res.status(403).send('Invalid request');
  }

  // 验证通过,处理邮件数据
  const emailData = JSON.parse(req.body.toString());
  // ... 导入到主应用的逻辑
  res.status(200).send('OK');
});

app.listen(3000);

Python/Django 示例

import hmac
import hashlib
import time
import json
import os
from django.http import HttpResponseForbidden, HttpResponse
from django.views.decorators.csrf import csrf_exempt

SENDGRID_API_KEY = os.environ.get('SENDGRID_API_KEY')

def validate_sendgrid_request(request):
    timestamp = request.headers.get('X-Sendgrid-Timestamp')
    signature = request.headers.get('X-Sendgrid-Signature')

    if not timestamp or not signature:
        return False

    # 验证时间戳
    now = int(time.time())
    if abs(now - int(timestamp)) > 300:
        return False

    # 获取原始请求体
    raw_body = request.body.decode('utf-8')
    signed_payload = f"{timestamp}.{raw_body}".encode('utf-8')

    # 计算签名并对比(用compare_digest避免时序攻击)
    computed_signature = hmac.new(
        SENDGRID_API_KEY.encode('utf-8'),
        signed_payload,
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(computed_signature, signature)

@csrf_exempt
def inbound_parse_webhook(request):
    if request.method != 'POST':
        return HttpResponse(status=405)
    
    if not validate_sendgrid_request(request):
        return HttpResponseForbidden('Invalid request')
    
    # 处理邮件数据
    email_data = json.loads(request.body)
    # ... 导入到主应用的逻辑
    return HttpResponse('OK')

四、关键注意事项

  • API密钥安全:绝对不要把API密钥硬编码在代码里,用环境变量、密钥管理服务存储,避免泄露。
  • 原始请求体:很多Web框架会自动解析请求体,一定要配置中间件保留原始内容,否则签名会不匹配。
  • 时序攻击防护:用语言内置的安全对比方法(比如Node.js的timingSafeEqual、Python的hmac.compare_digest),不要直接用==对比字符串,防止攻击者通过时序差异破解签名。

内容的提问来源于stack exchange,提问作者kdeez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 10:34:20