For a current project I'm implementing Stripe with a NestJS API. One powerful feature of Stripe are webhooks. To make them secure, each webhook request from Stripe contains a signature in the stripe-signature
header. This way it is possible to verify that the webhook request really originates from Stripe. See the Stripe docs for reference.
In Node for example to verify the webhook payload you would use the stripe.webhooks.constructEvent(request.body, signature, endpointSecret)
method by passing the raw request body, the mentioned signature header and the endpoint secret obtained in from the Stripe dashboard. And this is where the trouble begins...
NestJS and the raw body
NestJS uses body-parser by default which - you might have guessed it - results in a JSON parsed body. However Stripe's constructEvent()
method expects the raw request body, which (unfortunately) hasn't been around in the request object of Express for ages (for good reasons). As NestJS uses Express under the hood by default, you won't find the raw request body there either.
Let's add the raw request body then
After trying (and failing) to convert the JSON body to something Stripe might accept as a raw body, I stumbled upon this Stack Overflow question and implemented a solution based on a modified and extended version of the currently accepted answer by MikingTheViking.
First I had to disable body-parser globally for the whole NestJS application in main.ts
.
// main.ts
const app = await NestFactory.create(AppModule, {
bodyParser: false,
});
Then (also in main.ts
) I re-enabled body-parser as a middleware with the verify
option set (see the body-parser docs). This allows me to add the raw request body to the request object as a string. As I only want to make rawBody
available if really needed, I check for the presence of the stripe-siganture
header first and just return if not set.
// main.ts
import * as bodyParser from 'body-parser';
...
const rawBodyBuffer = (req, res, buffer, encoding) => {
if (!req.headers['stripe-signature']) { return; }
if (buffer && buffer.length) {
req.rawBody = buffer.toString(encoding || 'utf8');
}
};
app.use(bodyParser.urlencoded({ verify: rawBodyBuffer, extended: true }));
app.use(bodyParser.json({ verify: rawBodyBuffer }));
The extra mile (optional but nice)
While this works fine and you can access req.rawBody
in your constructor with the help of Nest's @Req()
decorator, wouldn't it be nice to use something like @RawBody()
similar to @Body()
instead?
It would.
Without further ado, here comes your custom @RawBody()
decorator:
// raw-body.decorator.ts
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const RawBody = createParamDecorator(
(data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.rawBody || null;
},
);
I hope this solution might help someone else too, it is working great for me so far.