Webhook signature verification for stripe: Are you passing raw request body received from stripe?
So you have your stripe payments setup and everything works well. Now you want to setup webhooks for recurring payments. Webhook is a way for an app in our case stripe to provide another applications (like our endpoint) with real-time information. Stripe has excellent documentation on setting up webhooks. After you setup your webhook endpoint, one of the errors you might run into is, “Are you passing raw request body received from stripe?”
You have followed all the steps recommended in the official documentation and yet the error persists. This is what happened to me while integrating webhooks for recurring payments.
The error got me thinking how to receive an incoming request stream and what happens when you don’t use body-parser which usually does the job of extracting the request stream and making it available on req.body. Reading and digging a little more about streams in Node.js revealed:
“The
request
object that's passed in to a handler implements theReadableStream
interface. This stream can be listened to or piped elsewhere just like any other stream. We can grab the data right out of the stream by listening to the stream's'data'
and'end'
events.The chunk emitted in each
'data'
event is aBuffer
. If you know it's going to be string data, the best thing to do is collect the data in an array, then at the'end'
, concatenate and stringify it” — Node.js official documentation
Use the middleware listed below to capture the incoming request stream, since this endpoint is not passed through any body-parser as stripe requires raw body into constructEvent() to verify the stripe signature.
const express = require("express");
const router = express.Router();
const db = require('../../data/helpers/subDb');
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const endpointSecret = process.env.STRIPE_ENDPOINT_SECRET;
//middleware router.use((req, res, next)=> { var data_stream ='';
// Readable streams emit 'data' events once a listener is added req.setEncoding('utf-8')
.on('data', function(data) {
data_stream += data;
})
.on('end', function() {
req.rawBody
req.rawBody = data_stream;
next();
})
});// The 'end' event indicates that the entire body has been received
Every time there is an incoming request stream, req.on(‘data’) is triggered and the incoming data is captured. When the stream ends, req.on(‘end’) is triggered, inside which you can attach the data_stream to req.rawBody which can be later accessed inside the webhook endpoint and passed to the constructEvent() for stripe signature verification.
Below is an example of a webhook endpoint for recurring payments where the subscription status is updated on receiving a successful payment event from stripe. You can setup another endpoint for payment failure.
//webhook endpoint router.post('/paymentsuccess', (req, res)=>{ // Retrieve the request's body and parse it as JSON, JSON.parse(req.rawBody).data is a huge object which consists of various stripe subscription related details that can be captured, id is stripe customer id in my project's subscription table that I require to update the payment status. const id = JSON.parse(req.rawBody).data.object.customer; let sig = req.headers['stripe-signature'];
try {
let evs = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);// Send recurring payment status update to database const status={stripe_subscription_status:'active'} const request= db.updateStripeCustomerStatus(id, status); request.then(response_data => {
console.log('response_data after recurring payment status update', response_data);
})
.catch(error => {
console.log(error.message);
})
}
catch (err) {
res.sendStatus(400).json({ error: err.message });
}
// Return a response to stripe to acknowledge receipt of the webhook event
res.sendStatus(200);
});
This should hopefully help verify the stripe signature without running into any errors. Make sure not to pass the webhook route through any body-parser at any point, since we want our request stream to remain raw.
Make sure to register each of your webhook endpoints on the stripe dashboard to get the STRIPE_ENDPOINT_SECRET which will be different for every endpoint and is required to pass into constructEvent(). Local endpoint as well as production endpoint registration is required, local endpoints can be setup through ngrook as mentioned in the stripe setup documentation.
I hope this article is helpful for developers verifying stripe signature for the first time and a very cool way to learn how to capture incoming request stream without using body-parser. I hope this article also sheds some light on how streams in Node.js work.