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, 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);
Thank you for providing your code.
The issue is here in the method’s params declaration, please take a look at the doc I shared in the previous message and describe all you request params in the JSDoc
also pay attention everything you return from the method will be considered as response body, so if your goal was to return HTTP Status Code 500 the following code will not work: