[Stripe] No signatures found matching the expected signature for payload.
I am trying to configure Stripe webhooks and it doesn't seem to work for me:
I configured the app:
StripeModule.forRoot(StripeModule, {
apiKey: stripeSecretKey,
webhookConfig: {
stripeWebhookSecret: stripeWebhookSecret,
// controllerPrefix: 'webhooks/stripe', // seems to have no effect
},
}),
And created a handler service:
@Injectable()
export class WebhookService {
@StripeWebhookHandler('*')
handlePaymentIntentCreated(evt: Stripe.Event) {
// execute your custom business logic
console.log(evt);
}
}
I used stripe-cli (./stripe listen --forward-to localhost:3000/stripe/webhook) to forward webhooks to my dev instance and triggered a webhook customer.updated event from the dashboard, which throws:
[Nest] 126333 - 04/23/2022, 12:55:43 PM LOG [NestApplication] Nest application successfully started +2ms
[Nest] 126333 - 04/23/2022, 12:55:53 PM ERROR [ExceptionsHandler] No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
at validateComputedSignature (/home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/stripe/lib/Webhooks.js:208:11)
at Object.verifyHeader (/home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/stripe/lib/Webhooks.js:103:5)
at Object.constructEvent (/home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/stripe/lib/Webhooks.js:10:20)
at StripePayloadService.tryHydratePayload (/home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/@golevelup/nestjs-stripe/src/stripe.payload.service.ts:23:39)
at StripeWebhookController.handleWebhook (/home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/@golevelup/nestjs-stripe/src/stripe.webhook.controller.ts:31:45)
at /home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/@nestjs/core/router/router-execution-context.js:38:29
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at /home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/@nestjs/core/router/router-execution-context.js:46:28
at /home/alko/Development/IvanOnTech/Moralis/moralis-billing-coordinator-stripe/node_modules/@nestjs/core/router/router-proxy.js:9:17
I've created a min repo from typescript starter: https://github.com/alko89/nest-stripe-webhook
I got the same issue and it was caused by bodyParser equals to true (needed for another service
I fixed it setting to false
const app = await NestFactory.create<NestExpressApplication>(AppModule, {
bodyParser: false,
});
I have the same issue but also have the bodyParser off like @displaynone is suggesting. Interestingly it seems to work when I use StripeCLI and a local deploy but if I deploy it on my test environment then it fails for some reason
@gogo199432 Can you elaborate your answer ? What is the difference between your local and test deployments ?
In my case, I set the bodyParser to false but forgot to comment the following lines referencing the body parse.
const app = await NestFactory.create(AppModule, { bodyParser: false });
// app.use(bodyParser.json({ limit: '5mb' })); <-- should also be commented
// app.use(bodyParser.urlencoded({ limit: '5mb', extended: true })); <-- should also be commented
In my case, I set the
bodyParserto false but forgot to comment the following lines referencing the body parse.const app = await NestFactory.create(AppModule, { bodyParser: false }); // app.use(bodyParser.json({ limit: '5mb' })); <-- should also be commented // app.use(bodyParser.urlencoded({ limit: '5mb', extended: true })); <-- should also be commented
@lapwat thanks, that solved the problem for me too. After that, how can the limit be reset?
@Karman40 I could not find a way to enable parsed body and raw body at the same time. So I use an interceptor that will check the root and parse the body if needed.
import { NestFactory } from '@nestjs/core';
import * as bodyParser from 'body-parser';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule, { bodyParser: false });
app.use((req: any, res: any, next: any) => {
// do not parse json bodies if we are hitting stripe controller
if (req.path.indexOf("/stripe/webhook") === 0) {
next();
} else {
bodyParser.json({ limit: '5mb' })(req, res, next);
}
});
app.use(bodyParser.urlencoded({ limit: '5mb', extended: true }));
...
}
bootstrap();
@lapwat Thanks, yes, this could be a perfect solution, I don't even know why I wanted to overcomplicate it.
In the meantime, I found another solution, which may be good for rawBody, but there is also an incompatibility with the body-parsel.
https://github.com/nestjs/nest/issues/10471
https://docs.nestjs.com/faq/raw-body#raw-body
Take a look to this: https://github.com/nestjs/nest/issues/10471
I believe this can be closed, but another tip for anyone who may be facing the same issue (even with bodyParser: false), if you're testing via the Stripe CLI, you'll need to use the webhook signing secret it that provides when running stripe listen .... I was using my endpoint key from the Stripe developer dashboard and encountered this error because of it.
-
Add
rawmiddleware: Include therawmiddleware for the '/webhook' endpoint before thejsonmiddleware. This ensures the raw request body is preserved for Stripe signature verification.import { json, raw } from 'express'; // Add this line before app.use(json({ limit: '50mb' })); app.use('/webhook', raw({ type: 'application/json' })); -
Work with buffers in the controller: In your controller handling the Stripe webhook, work with the buffer instead of parsing the JSON directly. Convert the buffer to a string and pass it to your webhook handling service.
@Post('webhook') async handleWebhook(@Req() req, @Res() res): Promise<HttpStatus> { const stripeSignature = req.headers['stripe-signature']; if (stripeSignature == null) { console.log('stripeSignature ERROR'); return; } // Work with the buffer to preserve the raw request body const buffer = req.body; const stripePayload = buffer.toString('utf8'); // Call your webhook handling service with the raw payload and signature const status = await this.transactionService.handleWebhookEvent( stripePayload, stripeSignature, ); res.status(status).end(); return status; }
Thanks to everyone providing helpful discussion and workarounds