Node.js中SendGrid Webhook签名验证持续失败问题排查
解决SendGrid Webhook签名验证失败问题
你的代码存在两个核心问题导致验证始终失败,以下是针对性修复方案:
1. 使用原始请求体而非解析后的对象
Firebase Cloud Function会自动将JSON请求体解析为JavaScript对象,但SendGrid的签名是基于原始UTF-8编码的请求体字符串生成的。解析再序列化后的字符串,会和原始payload的格式(比如键的顺序、空格、引号格式)产生差异,直接导致签名验证不通过。
必须获取原始请求体字符串进行验证:Cloud Function的req对象提供rawBody属性,直接转换为UTF-8字符串即可。
2. 公钥格式校验
确保环境变量SENDGRID_WEBHOOK_PK中的公钥是完整的PEM格式:
-----BEGIN PUBLIC KEY----- 你的公钥内容 -----END PUBLIC KEY-----
不要遗漏首尾的标记,也不要包含多余的换行或空格。
修正后的完整代码
import * as functions from "firebase-functions"; import { EventWebhook } from "@sendgrid/eventwebhook"; export const handleSendGridWebhookEvents = functions .region(...fb.REGIONS) .https.onRequest(async (req, resp) => { // 获取原始请求体字符串 const rawPayload = req.rawBody.toString("utf8"); const signature = req.get("x-twilio-email-event-webhook-signature") || ""; const timestamp = req.get("x-twilio-email-event-webhook-timestamp") || ""; if (!signature || !timestamp) { resp.status(400).json({ error: "Missing required SendGrid headers" }); return; } const verify = new EventWebhook(); const publicKey = process.env.SENDGRID_WEBHOOK_PK; if (!publicKey) { resp.status(500).json({ error: "SendGrid public key not configured" }); return; } try { const ecdsaPublicKey = verify.convertPublicKeyToECDSA(publicKey); // 用原始请求体执行验证 const isVerified = verify.verifySignature( ecdsaPublicKey, rawPayload, signature, timestamp ); if (!isVerified) { resp.status(403).json({ error: "Signature verification failed" }); return; } // 验证通过后再解析请求体处理事件 const payload = JSON.parse(rawPayload); console.log("Validated events:", payload); resp.status(200).json({ received: true, verified: true }); } catch (e) { console.error("Verification error:", e); resp.status(500).json({ error: "Webhook verification failed" }); } });
额外检查项
- 确认SendGrid控制台配置的Webhook URL和你的Cloud Function URL完全一致(必须是HTTPS)
- 测试时查看Cloud Function日志,排查是否有公钥格式错误、环境变量未加载等异常
- 使用SendGrid「Test Integration」工具测试时,确保请求未被中间代理篡改
内容的提问来源于stack exchange,提问作者Stf_F
相关产品推荐
相关产品推荐

