- Published on
Listen to Stripe webhooks with Directus.
- Authors
- Name
- Raphaël Becanne
- @rbecanne
Context
In order to listen to Stripe webhooks, you need to verify that the current request comes from Stripe by checking the signature in the header. Then you can read the body of the request, do what you need to and send a proper response to Stripe.
However, to verify the signature using: stripe.webhooks.constructEvent(request.body, sig, endpointSecret);
as mentioned in the documentation, you need to have request.body that is the raw request body. Otherwise you will end up with this error:
Webhook signature verification failed. 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
So you need to create two extension to properly read Stripe webhooks. One is a custom hook that will retrieve the raw body for you. The second is a custom endpoint to listen to Stripe Webhook and act accordingly. So your directus folder might look like:
.
├── extensions # All your extensions
├── endpoints # Listener for the Stripe webhooks
| └── stripe-webhook
| └── index.js
└── hooks # Your custom hook using a middleware to retrieve the raw request body
└── stripe-webhook-middleware
└── index.js
Create a custom hook
To create a custom hook, use the tool to create a Directus extension as mentioned in the documentation.
Then add this to create the hook. The solution comes from here and still works with Directus 9.18.
// index.js
const express = require('express')
module.exports = function registerHook({ init }) {
init('middlewares.before', async function ({ app }) {
app.use(
express.json({
verify: (req, res, buf) => {
// Change the path to your endpoint, for example: /stripe-webhook
if (req.originalUrl.startsWith('/path/to/endpoint')) {
req.rawBody = buf.toString()
}
},
})
)
})
}
This will transform every request going to your endpoint /path/to/endpoint
and add a rawBody that Stripe will be able to use.
Create a custom endpoint
Once you have your custom hook, you just have to adapt Stripe code example to read req.rawBody and not req.body.
For example, following the quickstart from Stripe:
export default (router, { services, exceptions }) => {
const { ItemsService } = services
const { ServiceUnavailableException } = exceptions
const stripe = require('stripe')('sk_test_SOMETHINGSECRET')
const endpointSecret = 'whsec_SOMETHINGSECRET'
router.post('/', async (req, res) => {
// MAIN DIFFERENCE WITH STRIPE EXAMPLE
let event = req.rawBody
// 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']
try {
event = stripe.webhooks.constructEvent(
req.rawBody, // MAIN DIFFERENCE WITH STRIPE EXAMPLE
signature,
endpointSecret
)
} catch (err) {
console.log(`⚠️ Webhook signature verification failed.`, err.message)
return res.sendStatus(400)
}
}
// 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
default:
// Unexpected event type
console.log(`Unhandled event type ${event.type}.`)
console.log(`The data object of the event: ${event.data.object}`)
}
// Return a 200 response to acknowledge receipt of the event
res.sendStatus(200)
})
}