How I Built a Stripe Webhook in Node.js (Full Guide)
Webhooks are essential for modern web applications that rely on real-time notifications from third-party services. Stripe, a popular payment processing platform, uses webhooks to notify your application about events like successful payments, failed charges, or subscription updates. In this guide, I'll walk you through building a robust Stripe webhook handler in Node.js, complete with verification, logging, and error handling.
Prerequisites
Before diving in, ensure you have the following:
- Node.js installed (v16 or later recommended).
- A Stripe account with a test API key.
- Express.js for handling HTTP requests.
-
Stripe SDK installed (
npm install stripe).
Setting Up Your Project
Initialize a new Node.js project and install the required dependencies:
mkdir stripe-webhook
cd stripe-webhook
npm init -y
npm install express stripe body-parser dotenv
Create a .env file to store your Stripe secret key and webhook signing secret:
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
PORT=3000
Creating the Webhook Endpoint
First, set up an Express server to handle incoming Stripe webhook events:
const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();
const port = process.env.PORT || 3000;
// Middleware to parse raw body for webhook verification
app.use(bodyParser.raw({ type: 'application/json' }));
// Webhook endpoint
app.post('/webhook', async (req, res) => {
const signature = req.headers['stripe-signature'];
let event;
try {
// Verify the webhook signature
event = stripe.webhooks.constructEvent(
req.body,
signature,
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 succeeded:', paymentIntent.id);
break;
case 'payment_intent.payment_failed':
const failedPaymentIntent = event.data.object;
console.log('PaymentIntent failed:', failedPaymentIntent.id);
break;
// Add more cases as needed
default:
console.log(`Unhandled event type: ${event.type}`);
}
// Send a response to Stripe
res.json({ received: true });
});
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Verifying Webhook Signatures
A critical step in webhook implementation is verifying the authenticity of the incoming payload. Stripe signs each webhook request with a unique HMAC-SHA256 signature. The stripe.webhooks.constructEvent method ensures the payload is genuine by comparing the provided signature with the one generated using your webhook signing secret.
Key Points:
- Always use the raw body for verification.
- Never expose your webhook signing secret.
- Handle errors gracefully to avoid security vulnerabilities.
Handling Webhook Events
Stripe webhooks deliver a variety of event types (event.type). Your application should handle only the events relevant to your use case. In the example above, we handled payment_intent.succeeded and payment_intent.payment_failed. You can extend this to accommodate other events like customer.subscription.created or charge.refunded.
Here’s an enhanced example for subscription events:
app.post('/webhook', async (req, res) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
console.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
console.log('PaymentIntent succeeded:', paymentIntent.id);
break;
case 'payment_intent.payment_failed':
const failedPaymentIntent = event.data.object;
console.log('PaymentIntent failed:', failedPaymentIntent.id);
break;
case 'customer.subscription.created':
const subscription = event.data.object;
console.log('Subscription created:', subscription.id);
break;
case 'customer.subscription.deleted':
const deletedSubscription = event.data.object;
console.log('Subscription deleted:', deletedSubscription.id);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
res.json({ received: true });
});
Adding Logging and Error Handling
Robust logging and error handling are essential for debugging and monitoring. Use libraries like winston or bunyan for structured logging. Here’s how to integrate winston:
npm install winston
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'webhook.log' }),
],
});
app.post('/webhook', async (req, res) => {
const signature = req.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
req.body,
signature,
process.env.STRIPE_WEBHOOK_SECRET
);
} catch (err) {
logger.error('Webhook signature verification failed:', err.message);
return res.status(400).send(`Webhook Error: ${err.message}`);
}
logger.info(`Received event: ${event.type}`);
switch (event.type) {
case 'payment_intent.succeeded':
const paymentIntent = event.data.object;
logger.info('PaymentIntent succeeded:', paymentIntent.id);
break;
case 'payment_intent.payment_failed':
const failedPaymentIntent = event.data.object;
logger.error('PaymentIntent failed:', failedPaymentIntent.id);
break;
default:
logger.warn(`Unhandled event type: ${event.type}`);
}
res.json({ received: true });
});
Testing Your Webhook
1. Use Stripe CLI:
Install the Stripe CLI and forward webhooks to your local server:
stripe listen --forward-to localhost:3000/webhook
2. Trigger Test Events:
Use the Stripe CLI or Dashboard to trigger test events:
stripe trigger payment_intent.succeeded
Deploying to Production
When deploying your webhook handler to production:
- Use HTTPS to secure communication.
- Ensure your webhook signing secret is kept secure.
- Monitor logs for any unexpected behavior.
- Consider using a queue system (e.g., RabbitMQ or Redis) to handle events asynchronously.
Final Thoughts
Building a Stripe webhook handler in Node.js is straightforward but requires attention to detail, especially around security and error handling. By following this guide, you’ll have a robust system capable of handling real-time payment notifications and other Stripe events. Happy coding! 🚀
🚀 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)