How to Integrate Stripe CLI with Next.js inside Docker Containers


I recently added Stripe integration into my dockerized Next.js application.

In order to test webhooks locally, I wanted to incorporate Stripe CLI into my developent workflow.

I could have used the Stripe Dashboard (Developers > Webhooks) to manage these webhooks. However, in order to do this, I would need to expose my local server to the public internet over some secure tunnel. While there are services dedicated to exactly this (e.g. ngrok, localtunnel), it requires extra steps to configure the endpoint and update the dashboard each time I start developing.

The natural solution was to use Stripe’s officially supported Stripe CLI.

I’ve been running my Next.js and PostgreSQL instances in containers spun up with docker-compose, so I opted to try out Stripe CLI’s official Docker image.

Suppose this is what I’m working with.

version: "3"
services:
  next-app:
    image: custom-next-image
    container_name: next-app
    networks:
      - next-postgres-network
    ports:
      - 3000:3000
  postgres-db:
    image: postgres
    container_name: postgres-db
    networks:
      - next-postgres-network
    ports:
      - 5432:5432
    volumes:
      - pg-data:/var/lib/postgresql/data
networks:
  next-postgres-network:
    driver: bridge
volumes:
  pg-data:
    driver: local

We have two services, next-app and postgres-db, that live under the next-postgres-network network.

For the sake of brevity, I won’t go over how to set up Next.js with Docker for development. I’ll assume we have an image built and ready to run.

  • next-app runs some custom Next.js application built prior to running docker-compose up.
  • postgres-db is just pulling the postgres image from Docker Hub.

Set up Stripe CLI in docker-compose

We’ll want to add another service for Stripe CLI.

stripe-cli:
  image: stripe/stripe-cli
  container_name: stripe-cli
  networks:
    - next-prisma-postgres-network

However, to force Stripe CLI to start listening for webhook events, we need to execute a command on container startup. docker-compose allows us to use the command field in each service.

stripe-cli:
  image: stripe/stripe-cli
  container_name: stripe-cli
  networks:
    - next-prisma-postgres-network
  command: <some_command>

Configure Stripe CLI

According to the docs, we can use stripe listen to listen for webhook events.

Inside the Docker container, we can just use listen.

There are three things we need to do before we set the command:

  1. Add our STRIPE_SECRET_KEY to our .env. This is accessible from the Stripe Dashboard.
  2. Add our STRIPE_DEVICE_NAME to our .env. This will be how you identify the specific device that events are being sent to (e.g. personal-laptop, work-laptop, etc.). This is viewable at Developers > Webhooks in the Stripe Dashboard.
  3. Identify the API route that will handle all the webhook events. In this example, we’ll assume all events will be POST requests to /api/stripe/webhook.

Assuming we’ve done all the above, we can then replace <some_command> in our Stripe CLI service to be the command below.

listen --api-key ${STRIPE_SECRET_KEY} \
--device-name ${STRIPE_DEVICE_NAME} \
--forward-to next-app:3000/api/stripe/webhook

The Stripe CLI documentation contains more information on specific flags we can use to configure our setup.

Configure Next.js Webhook Secret

Once we’re satisfied, we can start up our containers. We’ll notice that the Stripe CLI container outputs a webhook secret key upon start up.

Ready! Your webhook signing secret is whsec_xxx

We’ll want to take this secret key and set it as an environment variable in our Next.js applicaation.

# .env
...
STRIPE_WEBHOOK_SECRET=whsec_xxx

This is the secret we use when parsing and constructing the event passed into /api/stripe/webhook.

...
const buf = await buffer(req);
const sig = req.headers["stripe-signature"];
const event = stripe.webhooks.constructEvent(
  buf.toString(),
  sig,
  process.env.STRIPE_WEBHOOK_SECRET
);
...