DEV Community

Apollo
Apollo

Posted on

How I built a Stripe Webhook in Node.js (Full Guide)

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:

  1. Node.js (v16 or later) installed.
  2. A Stripe account with access to the Stripe Dashboard.
  3. Stripe CLI installed for testing webhooks locally.
  4. 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
Enter fullscreen mode Exit fullscreen mode

Install the required dependencies:

npm install express body-parser stripe dotenv
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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}`);
});
Enter fullscreen mode Exit fullscreen mode

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 });
});
Enter fullscreen mode Exit fullscreen mode

Explanation:

  1. Signature Verification: Stripe signs each webhook event using your webhook secret. The constructEvent method verifies this signature to ensure the event is genuine.
  2. Event Handling: The switch statement handles different event types (payment_intent.succeeded, customer.subscription.created, etc.). You can expand this to include more event types.
  3. Response: Return a 200 status 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
Enter fullscreen mode Exit fullscreen mode

Authenticate the CLI:

stripe login
Enter fullscreen mode Exit fullscreen mode

Start forwarding events to your local server:

stripe listen --forward-to http://localhost:3000/webhook
Enter fullscreen mode Exit fullscreen mode

Trigger test events:

stripe trigger payment_intent.succeeded
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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
});
Enter fullscreen mode Exit fullscreen mode

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)