Setting up a Stripe webhook with Kaito
Since Kaito uses the standard Request/Response API, setting up a Stripe webhook is straightforward. There’s a bit more context at the bottom of this page if you’re curious why.
Example
context.ts
import {create} from '@kaito-http/core';
export const router = create({
getContext: async (req, head) => {
return {
req,
bodyAsText: () => req.text(),
};
},
});stripe.ts
import {router} from './context.ts';
import {KaitoError} from '@kaito-http/core';
import Stripe from 'stripe';
const stripeClient = new Stripe(process.env.STRIPE_SECRET_KEY!);
// Create a crypto provider for stripe to use, required in some runtimes that don't define `crypto.subtle` globally.
// If you're unsure, try without, and then bring it back if you get an error.
const webCrypto = stripeClient.createSubtleCryptoProvider();
// Notice how we don't define a body schema, we're using stripe's webhook logic to parse the body for us
// which requires the raw body as a string.
export const stripeWebhook = router.post('/webhook', async ({ctx}) => {
const sig = ctx.req.headers.get('stripe-signature');
if (!sig) {
throw new KaitoError(400, 'No signature provided');
}
const body = await ctx.bodyAsText();
const event = await stripeClient.webhooks.constructEventAsync(
body,
sig,
process.env.STRIPE_ENDPOINT_SECRET!, // You should validate this exists, and not use the `!` operator
undefined,
webCrypto,
);
// Handle different event types
switch (event.type) {
case 'payment_intent.succeeded':
// Handle successful payment
break;
// Add other event types as needed
}
});For production webhooks, always implement proper error handling and logging. Stripe will retry failed webhook deliveries, so make sure your endpoint is idempotent and can handle duplicate events.
Why does this work so well?
Older versions of Kaito were built on Node.js’s IncomingMessage, and the body got parsed automatically before your route code even ran. That made it really annoying to get the raw body for things like Stripe webhook verification.
Now that Kaito uses the Fetch API Request object, you call req.text() whenever you want the raw body. That’s it.
Last updated on