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

如何在TypeScript版Azure Functions中获取HttpRequest原始Body用于Stripe Webhook验证

在TypeScript v4 Azure Function App中验证Stripe Webhook的正确方案

问题根源

Stripe Webhook签名验证依赖原始请求体的字节内容,任何对请求体的解析、格式化或编码转换都会导致签名不匹配。你尝试的几种方法都会修改原始请求体的结构,因此无法通过验证。

正确实现步骤

直接读取请求体的原始字节流,转换为Buffer后用于签名验证,具体代码示例如下:

1. 安装依赖

确保已安装必要的包:

npm install stripe @azure/functions

2. 完整函数代码

import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
import Stripe from "stripe";

// 初始化Stripe客户端
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-06-20", // 替换为你使用的Stripe API版本
});

// 工具函数:将ReadableStream转换为原始Buffer
async function streamToBuffer(stream: NodeJS.ReadableStream): Promise<Buffer> {
  return new Promise((resolve, reject) => {
    const chunks: Uint8Array[] = [];
    stream.on("data", (chunk) => chunks.push(chunk));
    stream.on("end", () => resolve(Buffer.concat(chunks)));
    stream.on("error", reject);
  });
}

// Stripe Webhook处理函数
export async function stripeWebhook(request: HttpRequest, context: InvocationContext): Promise<HttpResponseInit> {
  try {
    // 获取Stripe签名头
    const signature = request.headers.get("stripe-signature");
    if (!signature) {
      return { status: 400, body: "Missing stripe-signature header" };
    }

    // 读取原始请求体Buffer
    const rawBody = await streamToBuffer(request.body as NodeJS.ReadableStream);

    // 验证签名并构造事件
    const event = stripe.webhooks.constructEvent(
      rawBody,
      signature,
      process.env.STRIPE_WEBHOOK_SECRET!
    );

    // 根据事件类型处理业务逻辑
    switch (event.type) {
      case "payment_intent.succeeded":
        const paymentIntent = event.data.object as Stripe.PaymentIntent;
        context.log(`Payment succeeded: ${paymentIntent.id}`);
        // 这里添加你的业务处理代码
        break;
      case "customer.subscription.created":
        const subscription = event.data.object as Stripe.Subscription;
        context.log(`Subscription created: ${subscription.id}`);
        // 处理订阅创建逻辑
        break;
      // 可添加更多事件类型处理
      default:
        context.log(`Unhandled event type: ${event.type}`);
    }

    return { status: 200, body: "Event processed successfully" };
  } catch (err) {
    context.error(`Verification failed: ${(err as Error).message}`);
    return { status: 400, body: `Webhook Error: ${(err as Error).message}` };
  }
}

// 注册HTTP触发器
app.http("stripeWebhook", {
  methods: ["POST"],
  authLevel: "anonymous", // 根据你的需求设置认证级别(如function/admin)
  handler: stripeWebhook,
});

关键注意事项

  • 必须使用原始Buffer:Stripe的HMAC签名是基于请求体的原始字节计算的,任何字符串转换或JSON解析都会破坏签名的一致性。
  • 环境变量配置:确保在Azure Function的应用设置中配置STRIPE_SECRET_KEY(Stripe后台获取的秘钥)和STRIPE_WEBHOOK_SECRET(Webhook端点设置里的签名秘钥)。
  • 避免自动解析请求体:Azure Functions v4默认不会自动解析请求体,无需额外配置,但不要在触发器中添加任何会修改请求体的中间件。

你之前方法的问题说明

  1. 解析JSON后再stringify:JSON.stringify会重新格式化JSON结构(如调整空格、换行符),生成的字符串与原始请求体字节不一致,导致签名验证失败。
  2. raw-body包使用错误:你错误地将request.body.toString()传入getRawBody,实际应直接传入request.body流对象(正确用法:const rawBody = await getRawBody(request.body)),但自行实现streamToBuffer更直观可控。
  3. request.text():该方法会将流转换为字符串,可能会自动处理编码(如转换换行符、去除BOM),导致字节内容与原始请求不匹配。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 21:06:02