Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to use Stripe API with Node.js

This guide covers setting up Stripe with Node.js from scratch: installing the SDK, configuring API keys, creating customers, processing payments with PaymentIntents, and handling errors. Everything runs in test mode with the 4242424242424242 test card. By the end you will have a working Express server that creates customers, accepts payments, and handles webhooks.

What you'll learn

  • How to install and configure the Stripe Node.js SDK
  • How to create customers and process payments with PaymentIntents
  • How to handle Stripe API errors properly
  • How to set up a basic webhook endpoint with signature verification
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Beginner6 min read15 minutesStripe Node.js SDK v14+, Node.js 18+, Express.js 4+March 2026RapidDev Engineering Team
TL;DR

This guide covers setting up Stripe with Node.js from scratch: installing the SDK, configuring API keys, creating customers, processing payments with PaymentIntents, and handling errors. Everything runs in test mode with the 4242424242424242 test card. By the end you will have a working Express server that creates customers, accepts payments, and handles webhooks.

Getting Started with the Stripe API in Node.js

The Stripe Node.js SDK is the official library for integrating Stripe into backend applications. It wraps the REST API with typed methods for every Stripe resource — customers, payments, subscriptions, and more. The SDK handles authentication, retries, and error formatting. This guide takes you from npm install to a working payment server.

Prerequisites

  • Node.js 18 or later installed
  • A Stripe account (free to create at stripe.com)
  • Your test API keys from Dashboard → Developers → API keys
  • Basic familiarity with JavaScript and Express.js

Step-by-step guide

1

Install the Stripe SDK and Express

Initialize a Node.js project and install the required packages.

typescript
1# Create project and install dependencies
2mkdir stripe-node-app && cd stripe-node-app
3npm init -y
4npm install stripe express dotenv

Expected result: A Node.js project is created with stripe, express, and dotenv installed.

2

Configure Stripe with environment variables

Create a .env file with your test API keys and initialize the Stripe client.

typescript
1// .env
2// STRIPE_SECRET_KEY=sk_test_your_key_here
3// STRIPE_PUBLISHABLE_KEY=pk_test_your_key_here
4// STRIPE_WEBHOOK_SECRET=whsec_your_secret_here
5
6// server.js
7require('dotenv').config();
8
9const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, {
10 apiVersion: '2024-12-18.acacia',
11 maxNetworkRetries: 2,
12});
13
14const express = require('express');
15const app = express();
16
17console.log('Stripe initialized. API version:', stripe.getApiField('version'));

Expected result: The Stripe client is initialized with your test secret key and a pinned API version.

3

Create a customer

Customers are the foundation for payments, subscriptions, and saved cards. Create one with an email and metadata.

typescript
1app.use(express.json());
2
3app.post('/api/customers', async (req, res) => {
4 try {
5 const customer = await stripe.customers.create({
6 email: req.body.email,
7 name: req.body.name,
8 metadata: {
9 app_user_id: req.body.userId,
10 },
11 });
12 res.json({ customerId: customer.id, email: customer.email });
13 } catch (err) {
14 res.status(400).json({ error: err.message });
15 }
16});

Expected result: A Stripe customer is created and the customer ID is returned.

4

Create a PaymentIntent

PaymentIntents are the recommended way to accept payments. Create one on the server and send the client_secret to the frontend.

typescript
1app.post('/api/create-payment-intent', async (req, res) => {
2 try {
3 const { amount, customerId } = req.body;
4
5 const paymentIntent = await stripe.paymentIntents.create({
6 amount: amount, // Amount in cents (e.g., 2500 = $25.00)
7 currency: 'usd',
8 customer: customerId,
9 automatic_payment_methods: { enabled: true },
10 metadata: {
11 order_id: req.body.orderId,
12 },
13 });
14
15 res.json({
16 clientSecret: paymentIntent.client_secret,
17 paymentIntentId: paymentIntent.id,
18 });
19 } catch (err) {
20 res.status(400).json({ error: err.message });
21 }
22});
23
24// Test with: curl -X POST http://localhost:3000/api/create-payment-intent \
25// -H 'Content-Type: application/json' \
26// -d '{"amount": 2500, "orderId": "order_123"}'

Expected result: A PaymentIntent is created and the client_secret is returned for frontend confirmation.

5

Handle Stripe API errors

Stripe throws specific error types. Handle them to return useful error messages.

typescript
1async function handleStripeError(err, res) {
2 switch (err.type) {
3 case 'StripeCardError':
4 // Card was declined
5 res.status(402).json({
6 error: err.message,
7 code: err.code, // e.g., 'card_declined', 'expired_card'
8 decline_code: err.decline_code,
9 });
10 break;
11 case 'StripeRateLimitError':
12 res.status(429).json({ error: 'Too many requests. Please retry.' });
13 break;
14 case 'StripeInvalidRequestError':
15 res.status(400).json({ error: err.message });
16 break;
17 case 'StripeAuthenticationError':
18 console.error('Invalid API key!');
19 res.status(500).json({ error: 'Payment service configuration error.' });
20 break;
21 case 'StripeAPIError':
22 res.status(500).json({ error: 'Payment service temporarily unavailable.' });
23 break;
24 default:
25 res.status(500).json({ error: 'An unexpected error occurred.' });
26 }
27}

Expected result: Each Stripe error type is handled with an appropriate HTTP status code and user-friendly message.

6

Add a webhook endpoint

Handle payment confirmations and other events via webhooks. Remember to use express.raw() for the webhook route.

typescript
1app.post('/webhook',
2 express.raw({ type: 'application/json' }),
3 (req, res) => {
4 const sig = req.headers['stripe-signature'];
5 let event;
6 try {
7 event = stripe.webhooks.constructEvent(
8 req.body,
9 sig,
10 process.env.STRIPE_WEBHOOK_SECRET
11 );
12 } catch (err) {
13 return res.status(400).send(`Webhook Error: ${err.message}`);
14 }
15
16 if (event.type === 'payment_intent.succeeded') {
17 console.log('Payment confirmed:', event.data.object.id);
18 }
19
20 res.json({ received: true });
21 }
22);
23
24app.listen(3000, () => console.log('Stripe server running on port 3000'));

Expected result: Webhook endpoint verifies signatures and handles payment_intent.succeeded events.

Complete working example

server.js
1require('dotenv').config();
2
3const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY, {
4 apiVersion: '2024-12-18.acacia',
5 maxNetworkRetries: 2,
6});
7
8const express = require('express');
9const app = express();
10
11// Webhook route (must be before express.json())
12app.post('/webhook',
13 express.raw({ type: 'application/json' }),
14 (req, res) => {
15 const sig = req.headers['stripe-signature'];
16 let event;
17 try {
18 event = stripe.webhooks.constructEvent(
19 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
20 );
21 } catch (err) {
22 return res.status(400).send(`Webhook Error: ${err.message}`);
23 }
24 console.log('Event:', event.type, event.id);
25 if (event.type === 'payment_intent.succeeded') {
26 console.log('Payment confirmed:', event.data.object.id);
27 }
28 res.json({ received: true });
29 }
30);
31
32app.use(express.json());
33
34app.get('/api/config', (req, res) => {
35 res.json({ publishableKey: process.env.STRIPE_PUBLISHABLE_KEY });
36});
37
38app.post('/api/customers', async (req, res) => {
39 try {
40 const customer = await stripe.customers.create({
41 email: req.body.email,
42 name: req.body.name,
43 });
44 res.json({ customerId: customer.id });
45 } catch (err) {
46 res.status(400).json({ error: err.message });
47 }
48});
49
50app.post('/api/create-payment-intent', async (req, res) => {
51 try {
52 const pi = await stripe.paymentIntents.create({
53 amount: req.body.amount,
54 currency: 'usd',
55 customer: req.body.customerId,
56 automatic_payment_methods: { enabled: true },
57 });
58 res.json({ clientSecret: pi.client_secret });
59 } catch (err) {
60 res.status(400).json({ error: err.message });
61 }
62});
63
64app.get('/api/payments/:id', async (req, res) => {
65 try {
66 const pi = await stripe.paymentIntents.retrieve(req.params.id);
67 res.json({ id: pi.id, status: pi.status, amount: pi.amount });
68 } catch (err) {
69 res.status(400).json({ error: err.message });
70 }
71});
72
73const PORT = process.env.PORT || 3000;
74app.listen(PORT, () => console.log(`Stripe server on port ${PORT}`));

Common mistakes when using Stripe API with Node.js

Why it's a problem: Not pinning the API version in the Stripe client

How to avoid: Always pass apiVersion when initializing: require('stripe')(key, { apiVersion: '2024-12-18.acacia' }). This prevents unexpected breaking changes.

Why it's a problem: Using the secret key (sk_) in frontend code or committing it to Git

How to avoid: Store sk_ in environment variables and .env (add to .gitignore). Only pk_ keys go to the frontend.

Why it's a problem: Placing express.json() before the webhook route

How to avoid: The webhook route needs express.raw(). Define it before any express.json() middleware.

Why it's a problem: Passing amount as dollars instead of cents

How to avoid: Stripe amounts are always in the smallest currency unit. $25.00 = 2500 cents.

Best practices

  • Pin the Stripe API version to avoid unexpected breaking changes
  • Use environment variables for all API keys and webhook secrets
  • Handle all Stripe error types with appropriate HTTP status codes
  • Use maxNetworkRetries for automatic retry on transient network errors
  • Place the webhook route before express.json() middleware
  • Use metadata on every object to link Stripe resources to your internal records
  • Test with card 4242424242424242 (any future expiry, any CVC) in test mode
  • Use the Stripe CLI for local webhook testing: stripe listen --forward-to localhost:3000/webhook

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

How do I set up the Stripe API with Node.js? I need to install the SDK, configure it with environment variables, create customers, process payments with PaymentIntents, and handle webhooks. Show me a complete Express.js server.

Stripe Prompt

Build a complete Stripe payment server with Node.js and Express. I need endpoints for creating customers, creating PaymentIntents, retrieving payment status, and a webhook handler with signature verification. Include proper error handling.

Frequently asked questions

Which Node.js version does the Stripe SDK require?

The Stripe Node.js SDK v14+ requires Node.js 12 or later. We recommend Node.js 18+ for the best experience and long-term support.

How do I use Stripe with TypeScript?

The Stripe SDK includes TypeScript types. Install normally with npm install stripe and import with: import Stripe from 'stripe'. All resources and methods are fully typed.

What is the test card number for Stripe?

Use 4242424242424242 with any future expiration date and any 3-digit CVC. This card always succeeds in test mode. Use 4000000000000002 to simulate a declined card.

Do I need Express.js to use Stripe with Node.js?

No. The Stripe SDK works with any Node.js framework (Fastify, Koa, Hapi, etc.) or even without a framework. Express is used in this guide because it is the most common choice.

Can RapidDev help build my Stripe integration?

Yes. RapidDev specializes in building production-ready Stripe integrations including payments, subscriptions, Connect marketplaces, and webhook infrastructure.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.