Stripe webhook not working in firebase function - node.js

I'm setting up a stripe webhook to fulfill my order with stripe after a payment while hosting it with firebase functions.
When developing on a localhost everything works fine until I publish it in a firebase function where it always sends me a [400] error.
Creating the payment intent works fine in the same firebase function but that one doesn't.
The error message I get is : No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing
Code of my express backend :
app.post(
"/stripe/webhook",
bodyParser.raw({ type: "application/json" }),
async (request, response) => {
const payload = request.body;
const sig = request.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(payload, sig, endpointSecret);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the checkout.session.completed event
if (event.type === "checkout.session.completed") {
const session = event.data.object;
oldSession = await stripe.checkout.sessions.listLineItems(
session.id,
function (err, lineItems) {
fulfillOrder(session, lineItems);
}
);
}
response.status(200);
}
);
Anyone has an idea on how to make it work ? Thanks

I had the same problem but managed to find a solution that worked for me. First request the rawBody in a middleware using verify:
app.use(bodyParser.json({
verify: (req: any, res, buf, encoding) => {
// get rawBody
req.rawBody = buf.toString()
}
}))
Then parse the buffer as payload:
const payload = req.rawBody.toString()

request.body is a pre-parsed object of the request body and should not be used for verification. Instead you likely want request.rawBody which is a Buffer of the raw request body unparsed and unmodified. Try using that instead (you may need to convert it to a string) in your constructEvent call.

Related

Stripe API: Can´t use the raw body with express

So I´m trying to verify the transactions using Stripe and it needs the raw body to verify the request.
Stripe Docs on that topic: https://stripe.com/docs/webhooks/signatures
my code:
router.post(
"/stripe_webhook",
express.raw({ type: "application/json" }),
(request, response) => {
updateStripePaymentStatus(request, response);
}
);
exports.updateStripePaymentStatus = async (request, response) => {
const sig = request.headers["stripe-signature"];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
} catch (err) {
console.log(err);
response.status(400).send(`Webhook Error: ${err.message}`);
}
//Handle the event
switch (event.type) {
case "payment_intent.succeeded":
const paymentIntent = event.data.object;
const doc = await FileModel.findOne({ paymentId: paymentIntent.id });
doc.paymentStatus = paymentIntent.status;
doc.save();
break;
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}.`);
}
// Return a 200 response to acknowledge receipt of the event
response.send();
};
Error: message: 'No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing',
The secret and and so on should work and all. But if I console.log the request.body it still returns a jason object instead of a string...
Any ideas what could be wrong here?
It could be a number of things. There's probably something in your stack that modifying the request body. Take a look at these issues for some clues: https://github.com/stripe/stripe-node/issues/341 and https://github.com/stripe/stripe-node/issues/356.

Why stripe webhook is returning 400 error and how to fix it?

I am trying to get the webhook working for stripe but constantly getting the following error: Webhook Error: No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe? https://github.com/stripe/stripe-node#webhook-signing. I have followed their documentation and read through other SO users who had the same issue but still no success. Here is what I have in my code:
index.js
app.use('/webhooks/stripe', bodyParser.raw({type: "application/json"}), stripeWebhookRouter);
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json({
verify: function(req,res,buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe-webhooks')) {
req.rawBody = buf.toString()
}
}
}));
And here is my stripe webhook file that listen on certain endpoint
stripeWebhook.js
let stripeSecretKey;
if(process.env.NODE_ENV === 'production'){
stripeSecretKey = process.env.STRIPE_WEBHOOK_SECRET_PRODUCTION;
}else{
stripeSecretKey = process.env.STRIPE_SK_TEST;
}
const stripe = require('stripe')(stripeSecretKey);
const stripeWebhookRouter = Router();
stripeWebhookRouter.post('/stripeWebhook', async (request, response) => {
console.log("Stripe webhook working")
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = await stripe.webhooks.constructEvent(payload, sig, stripeSecretKey);
console.log("Stripe event constructed ", event);
} catch (err) {
console.log(`❌ Error message: ${err.message}`);
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// I have more code logic here, but it is irrelevant to this question
}
Am I setting up the bodyParser wrong for the Stripe router?
Okay fellow developers, I think I nailed the solution and if anyone faces similar issue please take look at my solution. I also strongly suggest that you have separate file for your router collection, because this will ensure that your index.js file is not clogged with bunch of routers.
So the issue appeared to be not being able to pass down raw incoming request to the stripe webhook endpoint. Here is my code change:
index.js
app.use('/webhooks/stripe', express.raw({type: "*/*"}), stripeWebhookRouter);
app.use((req, res, next) => {
if(req.originalUrl.startsWith('/stripe-webhooks')){
next();
}else{
express.json()(req, res, next);
}
})
and here us my stripeWebhook.js
const payload = request.body;
const sig = request.headers['stripe-signature'];
let event;
try {
event = await stripe.webhooks.constructEvent(payload, sig, stripeWebhookSecretKey);
console.log("✅ Stripe event constructed ", event);
} catch (err) {
console.log(`❌ Stripe event construction failed, error message: ${err.message}`);
return response.status(400).send(`Webhook Error: ${err.message}`);
}
Pay attention to how you parse incoming requests to your stripe webhook endpoint. Cheers!

Stripe Webhook 400 error. Raw request body issues

I've been following Stripes documentation on setting up webhooks and testing locally through their Stripe CLI. I can successfully create events, but I receive a response of 400 POST.
2021-12-14 23:18:53 --> payment_intent.created [evt_3K6ox6LqfpEEJtVG0yhTBKDf]
2021-12-14 23:18:53 <-- [400] POST http://localhost:4242/webhook [evt_3K6ox6LqfpEEJtVG0yhTBKDf]
No matter what I do, I always receive a "No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?" error. Looking through previous questions relating to this, the issue revolves around passing raw data.
I have doublechecked that my webhook secret matches that of the what the CLI gives me.
Here is the latest attempt:
// Use JSON parser for all non-webhook routes
app.use((req, res, next) => {
if (req.originalUrl === '/webhook') {
next();
} else {
express.json()(req, res, next);
}
});
// Stripe requires the raw body to construct the event
app.post('/webhook', express.raw({type: 'application/json'}), (req, res) => {
const sig = req.headers['stripe-signature'];
const rawBody = req.body
let event;
try {
event = stripe.webhooks.constructEvent( rawBody, sig, webhookSecret);
} catch (err) {
// On error, log and return the error message
console.log(`❌ Error message: ${err.message}`);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Successfully constructed event
console.log('✅ Success:', event.id);
// Return a response to acknowledge receipt of the event
res.json({received: true});
});
It's a common issue and could depend on your setup and framework. If you are using Node, I recommend going through this Github issue and see if workarounds there work for you.
Lastly you can write into Stripe Support and their dev could help figure out what went wrong.
For 2022, change the app.use(express.json()) to this.
app.use(express.json({
limit: '5mb',
verify: (req, res, buf) => {
req.rawBody = buf.toString();
}
}));
Then you have to change the webhook code to use the rawBody variable that was added correctly and remove the middleware express.raw({type:'application/json'}) since that won't be needed once you have the rawBody variable added to the request anyways.
app.post('/webhook', (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.rawBody, sig, endpointSecret); //#JA - Had to modify this to take the rawBody since this is what was needed.
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
return;
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
// Then define and call a function to handle the event payment_intent.succeeded
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
response.send();
});
The nice part about this fix is you can continue to use express.json() middleware to continue to parse the json from your API's as normal and none of your code should break :).

Stripe Webhook - Cannot read property 'type' of undefined

I just integrated the stripe webhook in node.
I am able to implement simple webhook endpoint and it is working absolutely fine.
But if I tried "Check webhook signature" I am getting the error: " Cannot read property 'type' of undefined"
My code looks just as provided by stripe:
app.post('/webhook', bodyParser.raw({type: 'application/json'}), (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
// event = request.body;
} catch (err) {
response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('PaymentIntent was successful!');
break;
default:
console.log(`Unhandled event type ${event.type}`);
// Unexpected event type
return response.status(400).end();
}
// Return a 200 response to acknowledge receipt of the event
response.json({ received: true });
});
I am not getting anything in the "event"
Please help me!!!
Try to use
bodyParser.raw({type: '*/*'})
https://github.com/stripe/stripe-node/issues/331
My issue was that I'm using NextJS and
I'm using Stripe to power the payment gateway of a Next.js app and got to the step to verify the webhook signing secret. I had some issues at first because Stripe expects the raw request body whereas Next.js parses the request body before it's passed to the handler.
https://maxkarlsson.dev/blog/2020/12/verify-stripe-webhook-signature-in-next-js-api-routes/

How to return both immediate and delayed response to slack slash command?

I'm trying to use a hook.io microservice to make a slack slash command bot. According to the docs I should be able to send an immediate response then a seperate POST later. But I cant get the immediate response and the later POST to both work.
Here is my test code.
module['exports'] = function testbot(hook) {
var request = require('request');
// The parameters passed in via the slash command POST request.
var params = hook.params;
data = {
"response_type": "ephemeral",
"text": "Immediate Response"
}
hook.res.setHeader('Content-Type', 'application/json');
console.log("returning immediate response")
hook.res.write(JSON.stringify(data), 'utf8', delay(params));
//calling end() here sends the immediate response but the POST never happens.
// but if end() is called below instead slack gives a timeout error but the POST succeeds.
//hook.res.end()
//test with 3.5 second delay
function delay(params) {
setTimeout(function () {post_response(params)},3500);
}
function post_response(params) {
console.log("posting delayed response")
// Set up the options for the HTTP request.
var options = {
// Use the Webhook URL from the Slack Incoming Webhooks integration.
uri: params.response_url,
method: 'POST',
// Slack expects a JSON payload with a "text" property.
json: {"response_type":"in_channel", "text":"Delayed response","parse":"full"}
};
// Make the POST request to the Slack incoming webhook.
request(options, function (error, response, body) {
// Pass error back to client if request endpoint can't be reached.
if (error) {
console.log(error);
hook.res.end(error.message);
} else {
console.log("post OK");
}
// calling end() here sends the POST but the immediate response is lost to a slack timeout error.
hook.res.end()
})
};
}
As detailed in the comments calling res.end() early means the immediate response gets sent but the POST never happens whereas delaying the res.end() until after POST means the delayed response is sent but it generates a timeout error from slack in the meantime.
I'm a javascript newbie so hopefully there is a simple solution that I've overlooked.
Once you call res.end() inside hook.io, the script will immediately abort and end processing. It's the equivalent of calling process.exit. If you fail to end the request, hook.io will eventually hit it's own Time-out Limit.
hook.io should be capable of responding back to Slack within the three seconds Slack requires.
Here is a guide which may help: Making a Custom Slack Slash Command with hook.io
Bad form to answer one's own question I know but the following worked for me using webtask so I include it here in case others find it useful.
var express = require('express');
var Webtask = require('webtask-tools');
var bodyParser = require('body-parser');
var request = require('request');
var app = express();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
//visiting above url in browser is handled by get
app.get('/', function (req, res) {
console.log(req.query)
res.send('OK');
});
//post from slack handled here
app.post('/', function (req, res) {
var params = req.body;
console.log(params);
var data = {
"response_type": "ephemeral",
"text": "Immediate Response"
}
res.setHeader('Content-Type', 'application/json');
res.send(data);
// deliberate delay longer than Slack timeout
// in order to test delayed response.
setTimeout(function () { post_response(params) }, 3500);
});
function post_response(params) {
console.log("posting delayed response")
// Set up the options for the HTTP request.
var options = {
// Use the Webhook URL supplied by the slack request.
uri: params.response_url,
method: 'POST',
// Slack expects a JSON payload with a "text" property.
json: { "response_type": "in_channel", "text": "Delayed response", "parse": "full" }
};
// Make the POST request to the Slack incoming webhook.
request(options, function (error, response, body) {
if (error) {
console.log(error);
} else {
console.log("post OK");
}
})
};
module.exports = Webtask.fromExpress(app);

Resources