golevelup / nestjs

A collection of badass modules and utilities to help you level up your NestJS applications 🚀

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Stripe webhooks - "undefined" is not valid JSON

JustDenP opened this issue · comments

Hello, everytime I see the same error after a stripe webhooks are sent to my nest server with local stripe cli. Anyone knows what causes the error and maybe how to fix it? Thank you!

ERROR [ExceptionsHandler] "undefined" is not valid JSON
SyntaxError: "undefined" is not valid JSON
    at JSON.parse (<anonymous>)
    at StripePayloadService.tryHydratePayload (/Users/denispenkov/Warehouse/dragon/node_modules/.pnpm/@golevelup+nestjs-stripe@0.8.0_@nestjs+common@10.3.8_@nestjs+core@10.3.8_rxjs@7.8.1_stripe@15.5.0/node_modules/@golevelup/nestjs-stripe/src/stripe.payload.service.ts:33:33)
    at StripeWebhookController.handleWebhook (/Users/denispenkov/Warehouse/dragon/node_modules/.pnpm/@golevelup+nestjs-stripe@0.8.0_@nestjs+common@10.3.8_@nestjs+core@10.3.8_rxjs@7.8.1_stripe@15.5.0/node_modules/@golevelup/nestjs-stripe/src/stripe.webhook.controller.ts:31:45)
    at /Users/denispenkov/Warehouse/dragon/node_modules/.pnpm/@nestjs+core@10.3.8_@nestjs+common@10.3.8_@nestjs+platform-express@10.3.8_reflect-metadata@0.2.2_rxjs@7.8.1/node_modules/@nestjs/core/router/router-execution-context.js:38:29

I have the default webhook without any logic

@Injectable()
export class PaymentWebhookService {
  constructor(
    @InjectStripeClient() private stripe: Stripe,
    private readonly paymentService: PaymentService,
  ) {}

  @StripeWebhookHandler('charge.succeeded')
  async handlePayment(event: Stripe.Event): Promise<void> {
    const dataObject = event.data.object as Stripe.Charge;
  }
}

@JustDenP
Hey, I encountered the same issue as well. Could you please let me know how you resolved it?

@Sky-FE Hi, the issue is related to undefined rawBody in request, make sure you set rawBody for these webhooks properly.

I use middleware for the specific endpoints:
raw-body.middleware.ts

import { RawBodyRequest } from '@nestjs/common';
import { json, Request, Response } from 'express';

export interface RawBodyMiddlewareOptions {
  limit: string;
  rawBodyUrls: string[];
}

export function rawBodyMiddleware(options: RawBodyMiddlewareOptions): unknown {
  const rawBodyUrls = new Set(options.rawBodyUrls);

  return json({
    ...options,
    verify: (request: RawBodyRequest<Request>, _: Response, buffer: Buffer) => {
      if (rawBodyUrls.has(request.url) && Buffer.isBuffer(buffer)) {
        request.rawBody = Buffer.from(buffer);
      }

      return true;
    },
  });
}

Then in your main.ts file you can use it before body parser if you have it

  app.use(
    rawBodyMiddleware({
      limit: '5mb',
      rawBodyUrls: ['/stripe/webhook', '/payment/test'],
    }),
  );
  app.use(bodyParser.json({ limit: '5mb' }));
  app.use(bodyParser.urlencoded({ limit: '5mb', extended: true }));

You can create any post endpoint to test it and make sure it is working and rawBody is there.

After that, if you will face another error "No signatures found matching the expected signature for payload", make sure to set up accountTest if you are using local stripe cli. I have it like this

webhookConfig: {
    decorators: [SkipThrottle()],
    requestBodyProperty: 'rawBody',
    stripeSecrets: {
      account: configService.get('stripe.account', { infer: true }),
      accountTest: configService.get('stripe.account', { infer: true }),
    },
  },

If you still have issues, you can write here, I also spent the whole day so set it right haha