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

如何在Fastify中验证Twilio签名?求成功实现经验指导

Fastify中Twilio签名验证失败的排查与修复方案

以下是针对你代码中可能存在的问题,给出的具体修复方案和排查要点:

核心问题排查点

  1. 反向代理下的URL构造错误:如果你的Fastify服务器部署在Nginx等反向代理后,req.protocol和req.headers.host可能指向代理服务而非Twilio实际访问的地址,导致签名验证时URL不匹配。
  2. 原始Body处理不规范:Fastify的rawBody默认是Buffer类型,直接转字符串可能存在编码问题;且Twilio发送的参数可能包含重复键,Object.fromEntries会覆盖这些值,导致验证失败。
  3. 参数解析逻辑缺失:Twilio的签名验证需要完整保留所有请求参数(包括重复字段),直接用URLSearchParams转对象会丢失多值参数。

修复后的代码示例

验证函数修改

import { validateRequest } from 'twilio';

export const verifyTwilioSignature = (req: FastifyRequest, reply: FastifyReply, done: () => void) => {
  const twilioSignature = req.headers['x-twilio-signature'] as string;
  // 适配反向代理场景,优先取转发头信息构造真实URL
  const protocol = req.headers['x-forwarded-proto'] || req.protocol;
  const host = req.headers['x-forwarded-host'] || req.headers.host;
  const fullUrl = `${protocol}://${host}${req.raw.url}`;
  const authToken = getEnvVar("TWILIO_AUTH_TOKEN");

  if (!authToken || !twilioSignature) {
    reply.status(403).send({ error: "缺少Twilio签名或AuthToken" });
    return;
  }

  // 确保原始Body正确转成UTF-8字符串
  const rawBody = typeof req.rawBody === 'string' 
    ? req.rawBody 
    : req.rawBody?.toString('utf-8');
  
  if (!rawBody) {
    reply.status(403).send({ error: "无法获取请求原始Body" });
    return;
  }

  // 正确解析参数,保留重复字段的数组格式
  const params = new URLSearchParams(rawBody);
  const paramsObj: Record<string, string | string[]> = {};
  params.forEach((value, key) => {
    if (paramsObj[key]) {
      paramsObj[key] = Array.isArray(paramsObj[key]) 
        ? [...paramsObj[key] as string[], value] 
        : [paramsObj[key] as string, value];
    } else {
      paramsObj[key] = value;
    }
  });

  try {
    const isValid = validateRequest(authToken, twilioSignature, fullUrl, paramsObj);
    if (!isValid) {
      reply.status(403).send({ error: "Twilio签名无效" });
      return;
    }
    done();
  } catch (err) {
    reply.status(403).send({ error: "签名验证过程出错" });
    return;
  }
};

路由配置确认

确保路由的rawBody配置生效,同时避免其他中间件篡改原始请求体:

export const twilioWebhookRoutes: FastifyPluginCallback = (app, _, done) => {
  app.post("/webhook/twilio/sms", {
    config: { rawBody: true },
    // 若使用了formbody插件,可临时禁用body解析确保rawBody完整
    // bodyParser: false,
    preHandler: verifyTwilioSignature,
    schema: twilioSmsWebhookSchema,
    handler: twilioSmsWebhook,
  });
  done();
};

额外验证步骤

  1. 确认TWILIO_AUTH_TOKEN与Twilio控制台中的值完全一致,无多余空格或拼写错误。
  2. 用Twilio提供的签名验证工具,对比你构造的fullUrl和参数是否与Twilio发送的完全匹配。
  3. 若服务器在反向代理后,确保代理服务已传递X-Forwarded-Proto和X-Forwarded-Host头。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 15:47:14