Implementing payments is often the most stressful part of launching a SaaS product. One wrong line of code and you’re either losing money or, worse, compromising user data. In my experience building several automation tools, I’ve found that while there are many best payment gateways for startups, Stripe remains the gold standard for developer experience.

If you’re looking for a comprehensive stripe api integration tutorial for nodejs, you’re in the right place. I’ve stripped away the fluff to give you a production-ready implementation that handles the two most critical parts of the flow: the Checkout session and the asynchronous confirmation via webhooks.

Prerequisites

Before we dive into the code, ensure you have the following set up:

Step 1: Project Initialization and SDK Installation

First, let’s initialize our project and install the necessary dependencies. I always recommend using a .env file to keep your secret keys out of version control.

mkdir stripe-nodejs-app
cd stripe-nodejs-app
npm init -y
npm install stripe express dotenv

Create a .env file in your root directory and add your Stripe secret key:

STRIPE_SECRET_KEY=sk_test_your_secret_key_here
PORT=3000

Step 2: Creating the Checkout Session

The most secure way to handle payments is Stripe Checkout. Instead of building your own credit card forms—which would put you in a nightmare of PCI compliance for developers—you redirect the user to a Stripe-hosted page.

Here is the implementation for the checkout route. As shown in the image below, we are configuring the line items and the success/cancel URLs.

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const express = require('express');
const app = express();

app.post('/create-checkout-session', async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.create({
      payment_method_types: ['card'],
      line_items: [
        {
          price_data: {
            currency: 'usd',
            product_data: { name: 'Pro Plan Subscription' },
            unit_amount: 2000, // $20.00
          },
          quantity: 1,
        },
      ],
      mode: 'payment',
      success_url: 'https://your-site.com/success',
      cancel_url: 'https://your-site.com/cancel',
    });

    res.json({ url: session.url });
  } catch (e) {
    res.status(500).json({ error: e.message });
  }
});
Stripe Dashboard showing the API keys section and the Checkout session configuration
Stripe Dashboard showing the API keys section and the Checkout session configuration

Step 3: Handling Post-Payment with Webhooks

A common mistake beginners make is relying on the success_url to fulfill an order. Users might close the tab before redirecting. The only reliable way to confirm payment is via Webhooks.

Webhooks are HTTP callbacks that Stripe sends to your server. To test this locally, I use Ngrok to create a tunnel to my local port 3000. This allows Stripe’s servers to ‘reach’ my local machine.

// IMPORTANT: Use express.raw() for the webhook route to verify the signature
app.post('/webhook', express.raw({type: 'application/json'}), (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) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  if (event.type === 'checkout.session.completed') {
    const session = event.data.object;
    console.log(`Payment successful for session: ${session.id}`);
    // Here is where you update your database to mark the user as 'Paid'
  }

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

If you find the webhook setup tricky, I’ve written a deeper dive on handling stripe webhooks that covers idempotency and retry logic.

Pro Tips for Production

Troubleshooting Common Issues

“Webhook Signature Verification Failed”

This usually happens because the request body was parsed by express.json() before it reached the webhook route. Stripe requires the raw request body to verify the signature. Ensure the webhook route is defined before any global app.use(express.json()) middleware.

“Invalid API Key”

Double-check that you aren’t using your Publishable Key (pk_…) where the Secret Key (sk_…) is required. The Node.js SDK always uses the Secret Key.

What’s Next?

Now that you have a basic payment flow, you can expand into recurring billing using Stripe Subscriptions. I recommend exploring Stripe Billing for automated invoicing and grace periods.

Ready to scale your app? Keep your code clean and your payments secure. If you have questions about the implementation, drop them in the comments!