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

  • Add node_compat = true to your wrangler.toml file. This will instruct wrangler to polyfill and bundle all of the necessary libraries Stripe needs to function, such as Buffer.
  • Initialise Stripe as follows. You will need your STRIPE_KEY secret set.

We need to do a few things here to get it working how we want, like setting the httpClient to a fetch compatible one, and overriding the CryptoProvider to use Web Crypto instead of Node's default crypto. The big difference here is that Web Crypto is async, so this will change how we verify our webhooks shortly.

import Stripe from 'stripe/lib/stripe.js';

// 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. 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
	}
});
You've successfully subscribed to James Ross
Great! Next, complete checkout for full access to James Ross
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.