Verifying Stripe Webhook Signatures with Cloudflare Workers
Stripe and Cloudflare Worker seem like the perfect pairing - easy payments for your edge apps! But hooking them together can be a little more tricky than it seems on the surface.
There's a blog post from Cloudflare a while ago detailing this, but things have changed since then with the release of Wrangler 2, and some changes on Stripe's end too.
Getting Started
- Firstly, you will need Wrangler 2. If you're still using Wrangler 1, I'd encourage you to update, but if you can not, follow the original Cloudflare blog post.
- I'm going to skip setting up Wrangler with your project, and jump straight to the configuration you will need to use. Follow Cloudflare's Developer Docs if you're new to Workers or Wrangler.
Setup
AddUPDATE: as ofnode_compat = true
to yourwrangler.toml
file. This will instruct wrangler to polyfill and bundle all of the necessary libraries Stripe needs to function, such as Buffer.stripe-node
versionv11.10.0
and higher,node_compat
is no longer required.- Initialise Stripe as follows. You will need your
STRIPE_KEY
secret set.
We need to do a couple of things here to get it working how we want, like setting the httpClient
to a fetch
compatible one, and exporting a WebCrypto CryptoProvider
provider to use for our webhook verification.
import Stripe from 'stripe';
// use web crypto
export const webCrypto = Stripe.createSubtleCryptoProvider();
export function getStripe({env}){
if(!env?.STRIPE_KEY){
throw new Error('Can not initialise Stripe without STRIPE_KEY');
}
const client = Stripe(env.STRIPE_KEY, {
httpClient: Stripe.createFetchHttpClient(), // ensure we use a Fetch client, and not Node's `http`
});
return client;
}
Usage
Stripe offers some details in the stripe-node
module for how to verify webhooks. They use the constructEvent
function synchronously.
Unfortunately, this doesn't work within Workers since WebCrypto is async, but they also include constructEventAsync
, despite not really being documented. We can use it as follows, with Stripe.createSubtleCryptoProvider
. You will need your STRIPE_ENDPOINT_SECRET
secret configured.
import {webCrypto, getStripe} from './helpers.js'; // from snippet above
// example using `itty-router`
router.post('/api/stripe/webhooks', async (req, env) => {
// request handler, where `request` is a Request, and `env` is your Env object with secrets
const stripe = getStripe({env});
const sig = req.headers.get('Stripe-Signature'); // get signature from header
// verify webhook. This will THROW if invalid
const event = await stripe.webhooks.constructEventAsync(
await req.text(), // raw request body
sig, // signature header
env.STRIPE_ENDPOINT_SECRET,
undefined,
webCrypto
);
switch(event.type){
// handle events
}
});