-
Notifications
You must be signed in to change notification settings - Fork 770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Webhook signature verification and bodyParser.json issue #341
Comments
@jlomas-stripe Would you mind providing some guidance/advice on this one? Thanks! |
This is the way I'm handling it: // main file
app
.set(...)
.use(...<helmet, cors, compression, logging...>)
.use('/stripe-webhooks', stripeWebhookRoutes) //the webhooks must come before the default json body parser
.use(bodyParser.json())
// webhook handling file
export const stripeWebhookRoutes = Router()
.use(bodyParser.raw({type: '*/*'}))
.post('', eventParser(process.env.STRIPE_WEBHOOK_SECRET), mainStripeWebhook)
.post('/connect', eventParser(process.env.STRIPE_WEBHOOK_CONNECT_SECRET), connectStripeWebhook);
function eventParser(secret) {
return (req, res, next) => {
try {
req.body = stripe.webhooks.constructEvent(
req.body.toString(),
req.headers['stripe-signature'],
secret);
} catch (error) {
return res.sendStatus(httpStatus.BAD_REQUEST);
}
return next();
}
} |
@ronzeidman Thanks for sharing your solution, mine is: app.use(bodyParser.json({
// Because Stripe needs the raw body, we compute it but only when hitting the Stripe callback URL.
verify: function(req,res,buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe-webhooks')) {
req.rawBody = buf.toString()
}
}})); At the end it does what we need but I think it should be provided in the doc as currently you might think that the example would be easily plug-able as is but it is not. |
This is a really good point, but I think it's going to be pretty specific to how your application is implemented. I guess there are a number of different ways this could be implemented; the idea of the documentation example was just to provide a way to get it working quickly. |
It might be good to highlight this in the example that proper care has to be done for the bodyParser. Especially in the official docs at https://stripe.com/docs/webhooks where there is no mention on how to set this up, and I think the following comment:
is actually confusing making you believe that it will work all the time because it is in its own route. |
Yep. I ran into this too. Still not sure how I want to handle it. |
Those docs were based on my example so ... this is my bad. I'll take a look and see if there's another/better/easier/more-consistent way to get at the raw body in Express without using |
Ok, so you can accomplish the same with just simple middleware: function addRawBody(req, res, next) {
req.setEncoding('utf8');
var data = '';
req.on('data', function(chunk) {
data += chunk;
});
req.on('end', function() {
req.rawBody = data;
next();
});
} |
I'm pulling my hair out and could really use some insight. I've tried all methods mentioned above (using bodyParser.raw, using the Note: this is an event for a connected account, I need to handle the I'm using the latest stripe package Here's my first middleware/route handler (no other middleware above this): app.post('/stripe-webhooks', bodyParser.raw({type: '*/*'}), postStripeWebhooks)
//...further down I do the standard, json parser (but even if I remove that it makes not difference)
app.use(bodyParser.json()) And const body = req.body.toString()
try {
const event = stripe.webhooks.constructEvent(body, req.headers['stripe-signature'], config.stripeWebhookSecret)
console.log(event)
}
catch(e) {
console.log('Error', e.message)
} Every way I go about it I get the same result: |
@joaoreynolds If you use the 'simple middleware' from the example, and use |
*epic facepalm I just thought, "know what? I better check my webhook secret key even though I'm sure I have that set". I had the wrong effing webhook secret key stored in my production environment variables!(That moment when you realize you're a terrible developer and nobody should hire you, haha) Thanks @jlomas-stripe for being willing to help. In an effort to be somewhat productive to this thread, after cleaning my code, I found that @manu-st 's version was the least-invasive to my app, allowing me to use |
You're very welcome - any time! :) |
My implementation in Meteor - thanks to @manu-st. import bodyParser from 'body-parser';
import { Picker } from 'meteor/meteorhacks:picker';
// Middleware declaration
Picker.middleware(bodyParser.json({
limit: '10MB',
verify: function(req,res,buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe-webhooks')) {
req.rawBody = buf.toString()
}
}
}));
// filtered main route
var postRoutes = Picker.filter(function(req, res, buf) {
var url = req.originalUrl;
if (url.startsWith('/stripe-webhooks')) {
// verify webhook's signature if he comes from stripe
// 'req.rawBody' available here
// Got only 'empty constructEvent' from official library ( let event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret); )
// adapt here https://github.com/stripe/stripe-node/blob/master/lib/Webhooks.js
// see details: https://stripe.com/docs/webhooks#verify-manually
}
// handle only POST requests
return req.method == "POST"
});
// from route, handling webhook and sending request
postRoutes.route('url', (request, response) => {
// handle result here
}); |
The issue is still present and driving me mad. |
@lomegg Can you provide more details on your issue and the specifics of your implementation, ie versions, where/how you're running it? |
@floatingLomas I am running it with express when getting a webhook after the simple cart charge (those are not required but I use them as confirmation in my eCommerce, so I need them to be signature verified). My issue is that I am getting object instead of json even after I use bodyParser as middleware like this Note that |
@lomegg What does |
@jlomas-stripe It provides some api methods, I was testing with it switched off to the same effect. But I will double check on that again, thanks |
Still getting that |
Best bet is to share the code in |
Thanks @manu-st for such an elegant solution - using It meant that we didn't have to refactor our Express routing. Great job! |
@lomegg I had the same issue and I found that another part of the application was adding in a |
@manu-st 's solution is what I got it working with. The thing is that most people's express setup is doing: app.use(bodyParser.json()); which sets the json parser for all body objects by default. So I just now pass a setup object to it that includes the block that @manu-st showed. app.use(bodyParser.json(setupForStripeWebhooks)); That object being: const setupForStripeWebhooks = {
// Because Stripe needs the raw body, we compute it but only when hitting the Stripe callback URL.
verify: function (req, res, buf) {
var url = req.originalUrl;
if (url.startsWith('/webhooks/stripe')) {
req.rawBody = buf.toString();
}
}
}; |
(posting this for visibility for people who find this through search engines) If you use express-generator to create your project, it will likely add the |
For those seeking solutions for Feathers.js (like I was) check out feathers-stripe-webhooks which will do all this for you 👍 |
Unfortunately this does not seem to be the case. I get the same problem even when using |
Why do these issues keep getting closed? Feels like there's a big issue with the signature validation code around needing the raw body. I am also having this problem but hard to find a solution when every issue gets diagnosed as user error. |
My adaptation of @kurry421's answer: The webhook:
My app.js:
A few things to keep in mind:
|
The raw-body package is an easy solution if you don't need body-parser. Then in your server setup you can just send the (req, res) tuple to your endpoint handler, as-is.
In stripehooks: [...]
|
Works if you are:
My solutionTry the following lines before you start up your express server:
Note:
|
The above example has been added as an example |
This was a lifesaver on firebase cloud functions. Thanks @mitchellbutler. |
Can I use that in Node Red ? And if so, where do I put it ? I tried in settings.js as follows, but when I deploy the flow I get an internal server error
|
Still doesn't work for me, still getting the same error. |
Doesn't work in GCP App engine, it parses it into JSON same as firebase does, but |
Verification of signature requires to have the data exactly the same like it was on the signer's machine. This could be guaranteed on string or buffer, but unserialised object does not guarantee e.g. properties order. |
Hey ! If you are on Typescript, here's a middleware that you can add to your route : interface RequestWithRawBody extends Request {
rawBody: Buffer;
}
const addRawBody = () => (req: Stream, _: Response, next: NextFunction) => {
const chunks: Buffer[] = [];
req.on("data", (chunk: Buffer) => {
chunks.push(chunk);
});
req.on("end", () => {
((req as unknown) as RequestWithRawBody).rawBody = Buffer.concat(chunks);
next();
});
}; |
hey friends if you are using the bodyParser in the app, the validation will not work.
replace with the code below
in the function to build the event pass the rawBody
after this change here it worked |
Many Many thanks Douglas, out of all solutions this one worked for me! |
using rawBody instead of body helps me |
I just wasted half a day on this! Moving the express.json() to the bottom worked! Thanks |
That's how i solved it:
|
I faced a similar issue and tried various approaches, including using body parser and exploring other options. Eventually, I discovered the Stripe GitHub examples repository, and its examples helped me resolve my issue.link |
add your specific url on it |
I ran into this issue also while using Google Cloud Functions. The link here helped me fix it : https://stackoverflow.com/questions/53899365/stripe-error-no-signatures-found-matching-the-expected-signature-for-payload
|
I've killed half a day learning how it should work. My stack is Firebase, Express, TypeScript. I do not use any JSON parsing middleware, but I ended up with this hack: // index.ts:
const app = express();
app.use(express.json({
verify: (req, res, buf) => {
(req as any).rawBody = buf; // <<
}
}));
const errorWrapper = (
fn: (req: Request, res: Response, next: NextFunction) => Promise<void>
) => (req: Request, res: Response, next: NextFunction) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
app.post("/api/subscription/webhook", errorWrapper(webhook));
const errorHandler = (err: Error, req: Request, res: Response, next: NextFunction) => {
console.error(err);
const correlationId = req.headers["x-correlation-id"];
res.status(500).json("Internal server error " + correlationId);
};
app.use(errorHandler); // webhook.ts
import { Request, Response } from "express";
import { getStripeApi } from "./stripeapi";
import Stripe from "stripe";
import { defineSecret } from "firebase-functions/params";
export const STRIPE_WEBHOOK_SECRET_KEY = defineSecret("STRIPE_WEBHOOK_SECRET_KEY");
export const webhook = async (req: Request, res: Response) => {
const sig = req.headers["stripe-signature"];
if (sig === undefined) {
console.error("Stripe signature is missing.");
res.status(400).send("Stripe signature is missing.");
return;
}
const stripe = getStripeApi();
const webhookSecret = STRIPE_WEBHOOK_SECRET_KEY.value();
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(req.rawBody, sig, webhookSecret);
} catch (error) {
console.error("Webhook signature verification failed.", error);
res.status(400).send(`Webhook Error: ${(error as any).message}`);
return;
}
//await handleEvent(event);
res.json({ received: true });
}; // stripeapi.ts
import Stripe from "stripe";
import { defineSecret } from "firebase-functions/params";
export const STRIPE_SECRET_KEY = defineSecret("STRIPE_SECRET_KEY");
let stripeApi: Stripe | null = null;
export const getStripeApi = () => {
return stripeApi ?? (stripeApi = new Stripe(STRIPE_SECRET_KEY.value(), { apiVersion: "2024-06-20" }));
}; // express.d.ts
import { Request } from "express";
declare module "express-serve-static-core" {
interface Request {
rawBody: Buffer; // <<
}
} |
The simplest thing that worked for me is to move the stripe webhook before using `app.use(bodyParser.json()): const app = express();
app.use(cors())
app.post('/your-webhook-path', bodyParser.raw({type: '*/*'}), (request, response) => {
// the rest of the webhook
})
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: true})); |
Good one this is more optimized solution as compare to getting static url of stripe hook and adding it in if else bloakc to it, you can also use express.raw instead of bodyparser it will work same. |
@alexstyl This works for me, after 10 hours at least, thank you |
If you use in your express app
bodyParser.json()
for all routes, and then have a dedicated route for stripe's webhook, then the second call tobodyParser.json({verify: ...})
as done in the example has no effect.I have the following code:
The StripeRoute is defined exactly as the example.
Unfortunately it doesn't work. Only if I comment out the first call to
bodyParser.json
does it work. Which mean that I cannot have a call tobodyParser
just for my route, I need to add theverify
function my top call tobodyParser.json
and to avoid having to pay for the conversion all the time check for the Url to match my stripe route.Maybe the doc should reflect that, and if it is supposed to work then some guidance as it doesn't for me.
Thanks!
The text was updated successfully, but these errors were encountered: