Production scaffold · Free tool
Stripe Webhook Generator
Generate a durable TypeScript, Express, and Prisma Stripe webhook scaffold with signature verification and retries.
Everything runs in this browser. Results, code, copy, and downloads are fully ungated.
Inputs
01 / configureEvents to handle
Choose the Stripe events your billing flow needs. At least one remains selected.
No API keys are requested or sent anywhere. Generation happens in this browser.
Live output
02 / inspect6 production-oriented files
Raw-body verification, durable storage, duplicate handling, background processing, and retries.
import express from 'express';
import Stripe from 'stripe';
import { Prisma } from '@prisma/client';
import { prisma } from './prisma';
const router = express.Router();
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY ?? '');
const acceptedEvents = new Set<string>([
"checkout.session.completed",
"invoice.payment_succeeded",
"invoice.payment_failed",
"customer.subscription.updated",
"customer.subscription.deleted"
]);
// Mount this router before app.use(express.json()). Stripe requires the raw body.
router.post(
'/webhooks/stripe',
express.raw({ type: 'application/json' }),
async (request, response) => {
const signature = request.header('stripe-signature');
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
if (!signature || !webhookSecret) {
return response.status(400).json({ error: 'Stripe signature configuration is missing.' });
}
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(request.body, signature, webhookSecret);
} catch {
return response.status(400).json({ error: 'Invalid webhook signature.' });
}
if (!acceptedEvents.has(event.type)) {
return response.status(200).json({ received: true, ignored: true });
}
try {
await prisma.stripeWebhookEvent.create({
data: {
id: event.id,
type: event.type,
apiVersion: event.api_version,
payload: event as unknown as Prisma.InputJsonValue,
},
});
} catch (error) {
// Stripe retries deliveries. Treat an already-stored event as success.
if (error instanceof Prisma.PrismaClientKnownRequestError && error.code === 'P2002') {
return response.status(200).json({ received: true, duplicate: true });
}
return response.status(500).json({ error: 'Could not persist the webhook.' });
}
// Acknowledge only after durable storage; processing happens out of band.
return response.status(200).json({ received: true });
},
);
export { router as stripeWebhookRouter };
Method and assumptions
The generated route verifies Stripe's signature against the raw request body, then stores the event before returning a successful response. That keeps acknowledgement fast without risking event loss.
Stripe can deliver the same event more than once. The event ID is the database primary key, so duplicates are acknowledged without repeating work. A separate worker claims pending records, invokes idempotent handlers, and schedules exponential retries.
From estimate to implementation
FastStaq already ships the production layer.
Use the free output now, then skip the weeks of wiring auth, billing, webhooks, support, and operations yourself.
Common questions
Do I paste my Stripe secret into this tool?
No. The generator never asks for credentials. The output uses environment-variable placeholders that you configure only inside your own application.
Why store an event before processing it?
Durable storage lets the endpoint acknowledge Stripe quickly and allows processing to retry independently if your business logic or a dependency fails.
Is the generated code production-ready?
It is a substantial production-oriented scaffold, but you still need to implement idempotent domain handlers, observability, worker scheduling, authorization rules, and tests for your application.
Keep the production checklist
Get the 32 checks FastStaq uses before real users pay—billing, webhooks, security, recovery, and more.