Get started
BETA
Browse docs
Guides

Test Stripe webhooks locally

Receive Stripe webhook events on your laptop during development — no deploy, no Stripe CLI quirks.

You want to point Stripe at your local dev server so events like payment_intent.succeeded and checkout.session.completed land directly on localhost:3000. JustTunnel exposes your dev server over HTTPS so Stripe can reach it.

Stripe sends events to a URL you configure in the dashboard. During development your server isn't reachable from the public internet. Deploying on every change is slow; the Stripe CLI's listen command works but has its own limitations. A tunnel is the closest thing to "what production will see."

Steps

1. Install the CLI

curl -fsSL https://justtunnel.dev/install | sh

2. Start your dev server

npm run dev
# → http://localhost:3000

3. Open a tunnel

justtunnel 3000

You'll see something like:

Tunnel established
  https://abc123.justtunnel.dev → http://localhost:3000

Ready to receive traffic.

4. Configure the webhook in Stripe

In the Stripe dashboard → Webhooks, add a new endpoint:

  • Endpoint URL: https://abc123.justtunnel.dev/api/webhooks/stripe
  • Events: the ones you care about, e.g. checkout.session.completed

5. Trigger an event

Use the dashboard's "Send test webhook" button or run a real checkout. Your local server receives the request immediately:

POST /api/webhooks/stripe 200

Example handler

A minimal Next.js route handler:

import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
 
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
 
export async function POST(req: NextRequest) {
  const body = await req.text();
  const sig = req.headers.get("stripe-signature")!;
 
  const event = stripe.webhooks.constructEvent(body, sig, webhookSecret);
 
  switch (event.type) {
    case "checkout.session.completed": {
      const session = event.data.object;
      // Fulfill the order.
      console.log("Payment received:", session.id);
      break;
    }
  }
 
  return NextResponse.json({ received: true });
}

Read the body as raw text before passing it to constructEvent — JSON parsing breaks signature verification.

Tips

  • Pin the URL with justtunnel 3000 --subdomain myapp so the Stripe endpoint config doesn't go stale between sessions. Reserved subdomains are a paid-plan feature; see Reserve a subdomain.
  • Run JustTunnel in its own terminal alongside your dev server.
  • Keep your STRIPE_WEBHOOK_SECRET in .env.local so it survives restarts.

On this page