如何在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默认不会自动解析请求体,无需额外配置,但不要在触发器中添加任何会修改请求体的中间件。
你之前方法的问题说明
- 解析JSON后再stringify:
JSON.stringify会重新格式化JSON结构(如调整空格、换行符),生成的字符串与原始请求体字节不一致,导致签名验证失败。 - raw-body包使用错误:你错误地将
request.body.toString()传入getRawBody,实际应直接传入request.body流对象(正确用法:const rawBody = await getRawBody(request.body)),但自行实现streamToBuffer更直观可控。 - request.text():该方法会将流转换为字符串,可能会自动处理编码(如转换换行符、去除BOM),导致字节内容与原始请求不匹配。
内容的提问来源于stack exchange,提问作者Aiden Dipple
相关产品推荐
相关产品推荐

