Reputation: 435
I am using the code provide by Stripe to test a webhook. The Stripe secret and the endpoint secret have been triple checked.
Stripe version: 6.19 Body-Parser: 1.19
When I test webhook on the Stripe dashboard I get the result: (Test webhook error: 400) No signatures found matching the expected signature for payload. Are you passing the raw request body you received from Stripe?
Any help would be appreciated.
var bodyParser - require('body-parser');
// Using Express
const app = require('express')();
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
// Set your secret key: remember to change this to your live secret key in production
// See your keys here: https://dashboard.stripe.com/account/apikeys
const stripe = require('stripe')('sk_test_VPw...');
// Find your endpoint's secret in your Dashboard's webhook settings
const endpointSecret = 'whsec_...';
// Use body-parser to retrieve the raw body as a buffer
const bodyParser = require('body-parser');
// Match the raw body to content type application/json
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); //NOT WORKING!
} 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;
// Fulfill the purchase...
handleCheckoutSession(session);
}
// Return a response to acknowledge receipt of the event
response.json({received: true});
});
Upvotes: 11
Views: 20144
Reputation: 103
For anyone using Next.js you should check stripe documentation for your local webhook secret and this is my code for anyone that will need it or you could use the example from stripe github nextjs example
// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import type { NextApiRequest, NextApiResponse } from 'next'
import NextCors from 'nextjs-cors';
import { config as localConfig } from '../../config'
import Stripe from 'stripe';
type Data = {
name?: string,
error?: string,
message?: string,
status?: string,
}
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse<Data>
) {
await NextCors(req, res, {
// Options
methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE'],
origin: '*',
});
const stripeInstance = new Stripe(localConfig.stripeSecretKey, { apiVersion: '2022-11-15' });
const endpointSecret = localConfig.stripeEndpointSecret
if (req.method === 'POST') {
// let event: Stripe.Event;
let event = req.body
// console.log(event)
// const clientReferenceId = req.body.data.object.client_reference_id
// console.log("clientReferenceId: ", clientReferenceId)
// Only verify the event if you have an endpoint secret defined.
// Otherwise use the basic event deserialized with JSON.parse
if (endpointSecret) {
// Get the signature sent by Stripe
const signature = req.headers['stripe-signature'] as string;
// console.log("signature: ", signature)
try {
const body = await buffer(req);
event = stripeInstance.webhooks.constructEvent(
body,
signature,
endpointSecret
);
} catch (err: any) {
console.log(`⚠️ Webhook signature verification failed.`, err.message);
// res.status(400).json({ error: err.message })
}
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log(`PaymentIntent for ${paymentIntent.amount} was successful!`);
// Then define and call a method to handle the successful payment intent.
// handlePaymentIntentSucceeded(paymentIntent);
break;
case 'payment_method.attached':
const paymentMethod = event.data.object;
// Then define and call a method to handle the successful attachment of a PaymentMethod.
// handlePaymentMethodAttached(paymentMethod);
break;
case 'checkout.session.async_payment_failed':
const checkoutSessionAsyncPaymentFailed = event.data.object;
// Then define and call a function to handle the event checkout.session.async_payment_failed
break;
case 'checkout.session.async_payment_succeeded':
const checkoutSessionAsyncPaymentSucceeded = event.data.object;
// Then define and call a function to handle the event checkout.session.async_payment_succeeded
break;
case 'checkout.session.completed':
const checkoutSessionCompleted = event.data.object;
// Then define and call a function to handle the event checkout.session.completed
break;
case 'checkout.session.expired':
const checkoutSessionExpired = event.data.object;
// Then define and call a function to handle the event checkout.session.expired
break;
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}.`);
}
res.status(200).json({ status: "success", message: "Webhook received" })
} else {
res.status(405).json({ status: "error", message: "Method not allowd" })
}
}
const buffer = (req: NextApiRequest) => {
return new Promise<Buffer>((resolve, reject) => {
const chunks: Buffer[] = [];
req.on('data', (chunk: Buffer) => {
chunks.push(chunk);
});
req.on('end', () => {
resolve(Buffer.concat(chunks));
});
req.on('error', reject);
});
};
Upvotes: 1
Reputation: 371
One other thing that could be going wrong (which was giving me the error) is that the production webhook key is being used with a test request, or vice versa.
Upvotes: 0
Reputation: 7198
Usually this is due to something on your side parsing or modifying the raw request string before the signature is checked(so the signature is computed against a modified string, not the exact one Stripe sent). In this case it looks like the JSON express middleware is doing that:
app.use(express.json());
.
Stripe has an example of using a raw bodyParser middleware on the webhook endpoint instead so that your code gets the raw string that's required :
// 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'];
let event;
try {
event = stripe.webhooks.constructEvent(req.body, 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});
});
Upvotes: 35
Reputation: 2506
One liner plus no deprecated bodyParser. Make sure to define your endpoint's parser before the generic one, aka express.json().
app.use('/stripe/webhook', express.raw({type: "*/*"}))
app.use(express.json())
Upvotes: 14
Reputation: 29
For those working with NextJS. Here is a solution I bumped on Reddit by one @ u/SiMFiCysed https://www.reddit.com/user/SiMFiCysed/
Upvotes: 2
Reputation: 31
How to get both parsed body and raw body in Express:
app.use(bodyParser.json({
verify: (req, res, buf) => {
req.rawBody = buf
}
}))
Thanks to: https://flaviocopes.com/express-get-raw-body/
Upvotes: 2