How I Built a Stripe Webhook in Node.js (Full Guide)
Stripe webhooks are essential for handling asynchronous events such as payment success, subscription updates, and charge failures. In this guide, I’ll walk you through building a robust Stripe webhook handler in Node.js, including event validation, error handling, and deployment considerations. This is a production-ready implementation that you can adapt for your own projects.
Prerequisites
Before diving in, ensure you have the following:
- Node.js (v16 or later) installed.
- A Stripe account with access to the Stripe Dashboard.
- Stripe CLI installed for testing webhooks locally.
- Basic knowledge of Express.js and REST APIs.
Step 1: Setting Up Your Node.js Project
Initialize a new Node.js project:
mkdir stripe-webhook
cd stripe-webhook
npm init -y
Install the required dependencies:
npm install express body-parser stripe dotenv
Step 2: Configuring Environment Variables
Create a .env file in the root of your project:
STRIPE_SECRET_KEY=sk_test_51...
STRIPE_WEBHOOK_SECRET=whsec_...
PORT=3000
Replace STRIPE_SECRET_KEY with your actual Stripe secret key, and STRIPE_WEBHOOK_SECRET with your webhook signing secret (found in the Stripe Dashboard under Webhooks).
Step 3: Setting Up Express.js
Create an index.js file:
const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
// Middleware to parse raw JSON for webhook handling
app.use('/webhook', bodyParser.raw({ type: 'application/json' }));
app.use(express.json());
app.listen(process.env.PORT, () => {
console.log(`Server running on port ${process.env.PORT}`);
});
This sets up a basic Express server and configures bodyParser to handle raw JSON for the webhook endpoint.
Step 4: Implementing the Webhook Handler
Add the webhook endpoint:
app.post('/webhook', async (req, res) => {
const sig = req.headers['stripe-signature'];
let event;
try {
// Verify the webhook signature
event = stripe.webhooks.constructEvent(
req.body,
sig,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error(`⚠️ Webhook signature verification failed.`, err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle the event
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('💰 PaymentIntent was successful:', paymentIntent.id);
break;
case 'customer.subscription.created':
const subscription = event.data.object;
console.log('📦 Subscription created:', subscription.id);
break;
// Add more event types as needed
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.json({ received: true });
});
Explanation:
-
Signature Verification: Stripe signs each webhook event using your webhook secret. The
constructEventmethod verifies this signature to ensure the event is genuine. -
Event Handling: The
switchstatement handles different event types (payment_intent.succeeded,customer.subscription.created, etc.). You can expand this to include more event types. -
Response: Return a
200status with{ received: true }to acknowledge the event.
Step 5: Testing with Stripe CLI
Install the Stripe CLI if you haven’t already:
npm install -g stripe-cli
Authenticate the CLI:
stripe login
Start forwarding events to your local server:
stripe listen --forward-to http://localhost:3000/webhook
Trigger test events:
stripe trigger payment_intent.succeeded
Console logs should confirm that your webhook handler is working.
Step 6: Production Considerations
1. Error Handling
Add robust error handling for database operations or external API calls:
try {
await someDatabaseOperation(event);
} catch (err) {
console.error(`Error processing event: ${event.type}`, err);
}
2. Webhook Retries
Stripe automatically retries failed webhook deliveries. Ensure your handler is idempotent to avoid duplicate processing.
3. Logging
Use a logging library like winston or pino for structured logs:
const logger = require('winston');
logger.info('💰 PaymentIntent was successful:', paymentIntent.id);
4. Security
Never log sensitive data (e.g., customer details). Use HTTPS in production to secure webhook communication.
5. Deployment
Deploy your app to a cloud provider like AWS, Heroku, or Vercel. Use environment variables to securely manage secrets.
Step 7: Advanced Features
1. Event Schema Validation
Use libraries like ajv to validate the event payload against Stripe’s schema:
const Ajv = require('ajv');
const ajv = new Ajv();
const validatePaymentIntent = ajv.compile(require('./schemas/payment_intent.json'));
if (!validatePaymentIntent(event.data.object)) {
console.error('Invalid PaymentIntent:', validatePaymentIntent.errors);
}
2. Rate Limiting
Protect your webhook endpoint from abuse using express-rate-limit:
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
});
app.use('/webhook', limiter);
3. Queue Processing
For resource-intensive tasks, queue events using bull or kue:
const Queue = require('bull');
const paymentQueue = new Queue('payments', 'redis://127.0.0.1:6379');
paymentQueue.process(async (job) => {
const event = job.data;
// Process event
});
Conclusion
Building a Stripe webhook handler in Node.js involves more than just handling events. You need to validate signatures, handle errors gracefully, and ensure scalability for production use. The implementation above provides a solid foundation that you can customize based on your application’s needs.
By following this guide, you’ll have a production-ready Stripe webhook handler that can securely process Stripe events, ensuring your application stays in sync with your customers’ financial activities.
🚀 Stop Writing Boilerplate Prompts
If you want to skip the setup and code 10x faster with complete AI architecture patterns, grab my Senior React Developer AI Cookbook ($19). It includes Server Action prompt libraries, UI component generation loops, and hydration debugging strategies.
Browse all 10+ developer products at the Apollo AI Store | Or snipe Solana tokens free via @ApolloSniper_Bot.
Top comments (0)