nestjs icon indicating copy to clipboard operation
nestjs copied to clipboard

[Stripe] No signatures found matching the expected signature for payload.

Open alko89 opened this issue 3 years ago • 4 comments

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

alko89 avatar Apr 23 '22 11:04 alko89

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,
  });

displaynone avatar May 14 '22 19:05 displaynone

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 avatar May 18 '22 23:05 gogo199432

@gogo199432 Can you elaborate your answer ? What is the difference between your local and test deployments ?

lapwat avatar Aug 18 '22 14:08 lapwat

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

lapwat avatar Aug 18 '22 15:08 lapwat

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

@lapwat thanks, that solved the problem for me too. After that, how can the limit be reset?

Karman40 avatar Oct 31 '22 13:10 Karman40

@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 avatar Nov 05 '22 12:11 lapwat

@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

Karman40 avatar Nov 05 '22 14:11 Karman40

Take a look to this: https://github.com/nestjs/nest/issues/10471

marcalj avatar Feb 23 '23 13:02 marcalj

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.

BrettSGA avatar Oct 12 '23 20:10 BrettSGA

  1. Add raw middleware: Include the raw middleware for the '/webhook' endpoint before the json middleware. 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' }));
    
  2. 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;
    }
    

oussamadhouib avatar Nov 27 '23 11:11 oussamadhouib

Thanks to everyone providing helpful discussion and workarounds

WonderPanda avatar Nov 27 '23 18:11 WonderPanda