Back to KB
Difficulty
Intermediate
Read Time
6 min

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

By Codcompass TeamΒ·Β·6 min read

Webhooks are essential for modern payment processing systems, and Stripe's implementation is particularly elegant. In this deep dive, I'll walk through building a production-grade Stripe webhook handler in Node.js with proper security, error handling, and queue processing.

Understanding Stripe Webhook Architecture

Stripe webhooks operate via HTTP POST requests to your endpoint when events occur in your Stripe account. The critical components:

  1. Event Object: JSON payload containing event metadata and relevant object data
  2. Signature Verification: HMAC-based security using your webhook secret
  3. Idempotency: Handling duplicate events safely
  4. Retry Logic: Built-in exponential backoff from Stripe

Initial Setup

First, install the Stripe Node.js library:

npm install stripe @types/stripe

Enter fullscreen mode Exit fullscreen mode

Configure your environment variables (I recommend using dotenv):

STRIPE_SECRET_KEY=sk_test_...
STRIPE_WEBHOOK_SECRET=whsec_...

Enter fullscreen mode Exit fullscreen mode

Core Webhook Handler Implementation

Here's the foundation of our webhook handler:

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

const app = express();

// Stripe requires raw body for signature verification
app.post('/webhook', bodyParser.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    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':
      await handlePaymentIntentSucceeded(event.data.object);
      break;
    case 'charge.failed':
      await handleChargeFailed(event.data.object);
      break;
    // ... handle other event types
    default:
      console.log(`Unhandled event type ${event.type}`);
  }

  // Acknowledge receipt
  res.json({received: true});
});

Enter fullscreen mode Exit fullscreen mode

Advanced Security Considerations

Replay Attack Protection

Stripe includes a timestamp in the signature header. We should verify it's recent:

const MAX_TOLERANCE_SECONDS = 300; // 5 m

πŸŽ‰ Mid-Year Sale β€” Unlock Full Article

Base plan from just $4.99/mo or $49/yr

Sign in to read the full article and unlock all 635+ tutorials.

Sign In / Register β€” Start Free Trial

7-day free trial Β· Cancel anytime Β· 30-day money-back