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)

Building a Stripe webhook in Node.js is essential for handling asynchronous events from Stripe, such as payment success, subscription cancellations, or failed charges. In this guide, I’ll walk you through the process step-by-step, ensuring you understand the technical nuances of implementing a secure and efficient webhook.


Prerequisites

Before diving into the code, ensure you have the following:

  1. Node.js installed (v16 or later recommended).
  2. A Stripe account (sign up at stripe.com).
  3. Basic knowledge of Express.js and JavaScript.

Install the required dependencies:

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

Step 1: Set Up Your Stripe Webhook Endpoint

First, configure your Stripe account to point to your webhook endpoint:

  1. Go to the Stripe Dashboard.
  2. Navigate to Developers > Webhooks.
  3. Add a new endpoint (e.g., https://yourdomain.com/webhook).
  4. Select the events you want to listen to (e.g., payment_intent.succeeded, invoice.payment_failed).

Stripe will generate a signing secret (whsec_...), which you’ll use to verify incoming webhook requests.


Step 2: Create the Express Server

Start by setting up an Express server in Node.js:

const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const crypto = require('crypto');
require('dotenv').config();

const app = express();

// Middleware to parse JSON bodies
app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

This server listens for incoming requests and parses the raw body for Stripe’s webhook signature verification.


Step 3: Implement Webhook Verification

Stripe sends a Stripe-Signature header with each webhook request. Use this header to verify the request’s authenticity:

const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post('/webhook', (req, res) => {
  const sig = req.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
  } catch (err) {
    console.error(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the event
  switch (event.type) {
    case 'payment_intent.succeeded':
      console.log('Payment succeeded!');
      break;
    case 'invoice.payment_failed':
      console.log('Payment failed!');
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  res.json({ received: true });
});
Enter fullscreen mode Exit fullscreen mode

The stripe.webhooks.constructEvent method ensures the payload is genuine and originated from Stripe.


Step 4: Handle Specific Events

Stripe sends various event types based on your subscription plan and configuration. Here’s how to handle two common events:

Payment Success (payment_intent.succeeded)

case 'payment_intent.succeeded':
  const paymentIntent = event.data.object;
  console.log(`PaymentIntent ID: ${paymentIntent.id}`);
  console.log(`Amount: ${paymentIntent.amount}`);
  break;
Enter fullscreen mode Exit fullscreen mode

Payment Failure (invoice.payment_failed)

case 'invoice.payment_failed':
  const invoice = event.data.object;
  console.log(`Invoice ID: ${invoice.id}`);
  console.log(`Attempt: ${invoice.attempt_count}`);
  break;
Enter fullscreen mode Exit fullscreen mode

You can extend this switch statement to handle more events.


Step 5: Test Your Webhook

Use Stripe’s CLI or Dashboard to test your webhook:

  1. Install the Stripe CLI:
   brew install stripe/stripe-cli/stripe
Enter fullscreen mode Exit fullscreen mode
  1. Start listening for events:
   stripe listen --forward-to localhost:3000/webhook
Enter fullscreen mode Exit fullscreen mode
  1. Trigger a test event:
   stripe trigger payment_intent.succeeded
Enter fullscreen mode Exit fullscreen mode

You should see the event logged in your server console.


Step 6: Deploy Your Webhook

Once tested locally, deploy your Node.js app to a production environment (e.g., Heroku, AWS, or Vercel). Update your Stripe webhook endpoint to point to your live URL.


Step 7: Securely Store Secrets

Never hardcode sensitive data like your Stripe secret key or webhook signing secret. Use environment variables:

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...
Enter fullscreen mode Exit fullscreen mode

Load these variables using dotenv:

require('dotenv').config();
Enter fullscreen mode Exit fullscreen mode

Step 8: Monitor and Scale

Monitor your webhook endpoint using tools like Sentry or New Relic. Handle errors gracefully and scale your infrastructure to accommodate higher traffic.


Conclusion

Building a Stripe webhook in Node.js is straightforward once you understand the core concepts of endpoint verification and event handling. By following this guide, you’ve created a secure and scalable solution to integrate Stripe into your application.


Full Code Example

const express = require('express');
const bodyParser = require('body-parser');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const crypto = require('crypto');
require('dotenv').config();

const app = express();

app.use(bodyParser.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;

app.post('/webhook', (req, res) => {
  const sig = req.headers['stripe-signature'];

  let event;

  try {
    event = stripe.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
  } catch (err) {
    console.error(`Webhook Error: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case 'payment_intent.succeeded':
      console.log('Payment succeeded!');
      break;
    case 'invoice.payment_failed':
      console.log('Payment failed!');
      break;
    default:
      console.log(`Unhandled event type: ${event.type}`);
  }

  res.json({ received: true });
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Now you’re ready to handle Stripe events like a pro! 🚀


🚀 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)