Get raw request body in custom controller

System Information
  • Strapi Version:
  • Operating System:
  • Database:
  • Node Version:
  • NPM Version:
  • Yarn Version:

Hello Friends,

I am trying to integrate Stripe Payment Gateway’s Webhook in my Strapi setup. Webhook is working and being called remotely. But Stripe requires the raw body content to construct the event which fails saying that:

‘No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? GitHub - stripe/stripe-node: Node.js library for the Stripe API.

I am sure you guys have gone through the issue if implemented the Stripe. Can help on this?
P.S. I am using version 4.0.1

Thanks.

2 Likes

Hello, I was trying to integrate with Stripe as well and I was running into the same problem. It seems like nobody answered it and the docs are not detailing this, but you can configure your middlewares.

If your ./config/middlewares.js looks reads the following:

module.exports = [
        'strapi::errors',
        'strapi::security',
        'strapi::poweredBy',
        'strapi::logger',
        'strapi::query',
        { name: 'strapi::body', config: { includeUnparsed: true } },
        'strapi::session',
        'strapi::favicon',
        'strapi::public',
    ]

basically, replacing the default strapi::body for the one with the config set.

Then, from your endpoint you can run:

const unparsed = require('koa-body/unparsed.js');

module.exports = {
    async webhook(ctx) {
        const unparsedBody = ctx.prequest.body[unparsed];
        const signature = ctx.request.headers['stripe-signature'];
        let event;

        try {
            event = stripe.webhooks.constructEvent(
                unparsedBody,
                signature,
                endpointSecret
            );
        } catch (err) {
            return ctx.badRequest(`Webhook Error: ${err.message}`);
        }

        // Return a response to acknowledge receipt of the event
        return { received: true };

I hope it helps!

6 Likes

Thank you for this solution! I’ve looked for hours and this one worked for Strapi v4. Bumping for the search algorithm or any others that are lost.

Thanks you made my day a little easier!

Unfortunately, unparsedBody is undefined for me.

const unparsed = require('koa-body/unparsed.js');
const unparsedBody = ctx.request.body[unparsed];
// console.log
unparsed:  Symbol(unparsedBody)
unparsedBody:  undefined

Using strapi v4.

@adrinr’s answer is right. Just wanted to add, that when retrieving the raw body, you don’t necessarily have to import the symbol from koa dependency. This will do as well:

const raw = ctx.request.body[Symbol.for("unparsedBody")];
4 Likes

Thanks, simple and works fine !

It worked! Thanks.

In Strapi V4 or in latest version ‘koa-body/unparsed.js’ is undefined and ‘koa-body/lib/unparsed’ does not work.

const unparsed = require('koa-body/unparsed.js');

The latest Working Code for Strapi v4.9.0:

// config/middleware.js
module.exports = [
  'strapi::errors',
  'strapi::security',
	'strapi::cors',
  'strapi::poweredBy',
  'strapi::logger',
  'strapi::query',
  {
    name: 'strapi::body',
    config: {
      patchKoa: true,
      multipart: true,
      includeUnparsed: true,
    },
  },
  'strapi::session',
  'strapi::favicon',
  'strapi::public',
];

To get raw data:

const raw = ctx.request.body[Symbol.for("unparsedBody")];
1 Like

Thank you so much !

I am trying to do this in Strapi version 4.12.1, in a custom controller in my plugin, but ctx.request.body[Symbol.for("unparsedBody")]; returns undefined. Here is my function so far:

'use strict';

const crypto = require("crypto");

module.exports = ({ strapi }) => ({
  async handle(ctx) {
    const secretKey = process.env.SHOPIFY_API_KEY;
    const hmac = ctx.request.get('x-shopify-hmac-sha256');
    const rawBody = ctx.request.body[Symbol.for("unparsedBody")];
    console.log("RAW BODY:", rawBody); // prints RAW BODY: undefined
}});

I have tried to google it, and searched the forum, but I can not find a way to get this to work. Can anyone help?

Oh no, I added the config to the wrong environment. This works perfectly. Sorry.

Here I needed to add a “.data”:
const raw = ctx.request.body.data[Symbol.for(“unparsedBody”)]

Having the same issue… I can read the “non-rawbody” but can’t read the raw body…
console.log(“Received Stripe webhook payload:”, unparsedBody); → UNDEFINED
console.log(“Received webhook payload:”, ctx.request.body); → THE STRIPE WEBHOOK CONTENT

But i can’t get it to work…

const stripe = require(‘stripe’)(process.env.STRIPE_SECRET_KEY);
const unparsed = require(“koa-body/lib/unparsed.js”);

module.exports = {
async webhook(ctx) {
const signature = ctx.request.headers[‘stripe-signature’];

	const unparsedBody = ctx.request.body[unparsed];

And in my middleware:
module.exports = [
‘strapi::errors’,
‘strapi::security’,
‘strapi::cors’,
‘strapi::poweredBy’,
‘strapi::logger’,
‘strapi::query’,
{
name: ‘strapi::body’,
config: {
includeUnparsed: true,
},
}, ‘strapi::session’,
‘strapi::favicon’,
‘strapi::public’,
];

When I add to middleware

{
name: ‘strapi::body’,
config: {
includeUnparsed: true,
},

I get error on put/post requests:

error: stream is not readable

What am I doing wrong?

OK, had ‘strapi::body’ twice, so above fixed. But I still have error:

Webhook Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? If a webhook request is being forwarded by a third-party tool, ensure that the exact request body, including JSON formatting and new line style, is preserved. Learn more about webhook signing and explore webhook integration examples for various frameworks at GitHub - stripe/stripe-node: Node.js library for the Stripe API.

My code is

import Stripe from 'stripe'
import unparsed from "koa-body/unparsed.js"

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY, {apiVersion: '2023-10-16'})

export default {
  receive: async (ctx, next) => {
    let event
    const signature = ctx.request.headers['stripe-signature']
    console.log(process.env.STRIPE_WEBHOOK_SECRET)
    try {
      event = stripe.webhooks.constructEvent(
        ctx.request.body[unparsed],
        signature,
        process.env.STRIPE_WEBHOOK_SECRET
      )
    } catch (err) {
      console.error('Webhook Error1', err.message)
      try {
        event = stripe.webhooks.constructEvent(
          ctx.request.body[Symbol.for('unparsedBody')],
          signature,
          process.env.STRIPE_WEBHOOK_SECRET
        )
      } catch (err) {
        console.error('Webhook Error2', err.message)
        ctx.response.status = 400
        ctx.body = `Webhook Error: ${err.message}`
        return
      }
    }
    console.log('Received event:', event)

    // Handle the event
    switch (event.type) {
      case 'charge.succeeded':
        const chargeSucceeded = event.data.object;
        console.log('chargeSucceeded', chargeSucceeded)
        // Then define and call a function to handle the event charge.succeeded
        break;
      // ... handle other event types
      default:
        console.log(`Unhandled event type ${event.type}`);
    }
    ctx.response.status = 200
  }
}

Can anyone help?

This works

middleware
  {
    name: 'strapi::body',
    config: {
      includeUnparsed: true,
    },
  },


controller 
    const sig = ctx?.request.headers['stripe-signature'];

    let event;

    try {
      const raw = request.body?.[Symbol.for('unparsedBody')];
      event = stripe.webhooks.constructEvent(raw, sig, envs.stripe.webhookKey);
    } catch (err) {

2 Likes
module.exports = [
	'strapi::logger',
	'strapi::errors',
	'strapi::security',
	'strapi::cors',
	'strapi::poweredBy',
	'strapi::query',
	{
		name: "strapi::body",
		config: {
			includeUnparsed: true,
		}
	},
	// 'strapi::body', - remove this
	'strapi::session',
	'strapi::favicon',
	'strapi::public',
];

This worked for me aswell in Strapi 4.14
Thanks !

Not sure why, it didnt work for me. I keep on receiving a 405
Any help is most appreciated!

> order/controllers/webhook.js

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const stripeWebhook = async (ctx) => {
  
  const stripeSig = ctx.request.headers['stripe-signature'];
  const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
  
  let event;
  
  try {
    const rawBody = ctx.request.body[Symbol.for("unparsedBody")];
    console.log("RAWBODY",rawBody)
    event = await stripe.webhooks.constructEvent(
      rawBody, stripeSig, endpointSecret
      );
  } catch (err) {
    ctx.response.status = 400;
    ctx.response.body = `Webhook Error: ${err.message}`;
    return;
  }
};
module.exports = { stripeWebhook };
> config/middleware.js

module.exports = [
        'strapi::errors',
        'strapi::security',
        'strapi::poweredBy',
        'strapi::logger',
        'strapi::query',
        { name: 'strapi::body', config: { includeUnparsed: true } },
        'strapi::session',
        'strapi::favicon',
        'strapi::public',
    ]
> order/routes/customerOrder.js

module.exports = {
  routes: [
    {
      method: 'POST',
      handler: 'order.createCheckoutSession',
      path: '/orders/checkout',
      config: {
        policies: [],
        middlewares: [],
      },
    },
    {
      method: 'POST',
      handler: 'webhooks.stripeWebhook',
      path: '/orders/webhook',
      config: {
        policies: [],
        middlewares: [], //'global::stripeRawBody', 
      },
    }
  ],
};
1 Like

Actually I was fussing around with the config/middleware.js and yes the guys above have the right solution. You have to place the following instead of strapi::body

‘strapi::cors’,
‘strapi::poweredBy’,
‘strapi::logger’,
‘strapi::query’,
// ‘strapi::body’,
// these lines are telling Strapi how to streamline Stripe body.
{
  name: ‘strapi::body’,
      config: {
          includeUnparsed: true,
          patchKoa: true,
          multipart: true,
      },
},
1 Like