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 myappso 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_SECRETin.env.localso it survives restarts.
Related
- Test GitHub webhooks locally
- Reserve a subdomain
justtunnel— base command reference