400 - Cannot read properties of undefined (reading 'body')

Hi support team, I need your help. I keep getting this error and I am not sure how to fix this. I am using “coding” and I have a javascript. Essentially, whenever I hit “Invoke”, I get this error.

For context, I am setting up. a SaaS and will receive payments via Stripe. So when I send a test webhook in Stripe CLI using this prompt: stripe trigger checkout.session.completed

I get an error in Stripe’s sandbox dashboard with this error: HTTP status code 400 - {
“code”: 0,
“message”: “Cannot read properties of undefined (reading ‘body’)”,
“errorData”: {}
}

Am I right by thinking Backendless is already parsing JSON for me into req.body, so req.rawBody will indeed be undefined unless I explicitly switch the body-parser to “raw” in Backendless lower-level settings (which is tricky in the Console, I tried to do it but unable to find a way, is it because I am on the free version?)

Please let me know if you have any questions and any clarification.

Hi @Nasser_Othman

Welcome to the Backendless community!
Could you please share what your code of the method look like

Regards,
Vlad

Most likely, you forgot to specify the parameters for the method

Hi, thank you so much for the warm welcome. I provided you below the full JS code. I removed the secret keys values. Since, I tried to look for environment variables to store my keys safely, but wasn’t able to do so. So in the meantime I hard-coded them in but later will find a safer alternative once we resolve this error code. Please let me know if you need anything from me.

// services/StripeWebhookService.js

const crypto = require('crypto');

class StripeWebhookService {
  /**
   * Main webhook endpoint
   * • POST /stripe-webhook  (use JSON body parser)
   * • returns { status, body }
   */
  async handleStripeEvent(req) {
    // 1) Hard-coded secrets (replace with secure storage later)
    const stripeSecretKey = '<I will use a variable environment later>';
    const webhookSecret   = '<I will use a variable environment later>';

    if (!stripeSecretKey || !webhookSecret) {
      return { status: 500, body: 'Server config error: missing Stripe secrets.' };
    }

    // 2) Grab body text and signature header
    // We fallback to stringified req.body since Raw mode isn't available
    const payload   = JSON.stringify(req.body || {});
    const sigHeader = req.headers?.['stripe-signature'];
    if (!sigHeader) {
      return { status: 400, body: 'Missing Stripe signature header.' };
    }

    // 3) Verify signature (HMAC-SHA256)
    // Stripe sends header in format: t=timestamp,v1=signature
    const parts = sigHeader.split(',').reduce((acc, pair) => {
      const [k, v] = pair.split('=');
      acc[k] = v;
      return acc;
    }, {});
    const signedPayload = `${parts.t}.${payload}`;
    const expectedSig = crypto
      .createHmac('sha256', webhookSecret)
      .update(signedPayload)
      .digest('hex');

    // timing-safe compare
    try {
      const sigBuf = Buffer.from(parts.v1, 'hex');
      const expBuf = Buffer.from(expectedSig, 'hex');
      if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
        return { status: 400, body: 'Invalid Stripe signature.' };
      }
    } catch {
      return { status: 400, body: 'Invalid Stripe signature format.' };
    }

    // 4) Parse the event JSON
    let event;
    try {
      event = JSON.parse(payload);
    } catch {
      return { status: 400, body: 'Invalid JSON payload.' };
    }

    // 5) Dispatch by event type
    try {
      switch (event.type) {
        case 'checkout.session.completed':
          await this._onCheckoutCompleted(event.data.object);
          break;
        case 'invoice.paid':
          await this._onInvoicePaid(event.data.object);
          break;
        case 'invoice.payment_failed':
          await this._onPaymentFailed(event.data.object);
          break;
        case 'customer.subscription.deleted':
          await this._onSubscriptionCanceled(event.data.object);
          break;
        default:
          console.log('Unhandled Stripe event:', event.type);
      }
      return { status: 200, body: 'Event processed' };
    } catch (err) {
      console.error('Error processing Stripe event:', err);
      return { status: 500, body: 'Internal Server Error' };
    }
  }

  // ────────────────────────────────────────────────────────
  // Example handler for checkout.session.completed
  // ────────────────────────────────────────────────────────
  async _onCheckoutCompleted(session) {
    console.log('✅ checkout.session.completed', session.id);
    // Here you can update your database, send emails, etc.
  }

  async _onInvoicePaid(invoice) {
    console.log('✅ invoice.paid', invoice.id);
    // Your logic here
  }

  async _onPaymentFailed(invoice) {
    console.log('⚠️ invoice.payment_failed', invoice.id);
    // Your logic here
  }

  async _onSubscriptionCanceled(subscription) {
    console.log('ℹ️ customer.subscription.deleted', subscription.id);
    // Your logic here
  }
}

Backendless.ServerCode.addService(StripeWebhookService);