FeaturesGuidesBlogDocumentationFreeGet Started

Test Stripe Webhooks Locally

Stripe CLI listen vs Dashboard test webhooks through a tunnel — signature verification and whsec secrets on localhost.

Why Stripe webhooks need special handling

Stripe sends webhook events when payments succeed, subscriptions change, invoices fail, and dozens of other lifecycle moments. Your handler must verify cryptographic signatures, respond quickly with HTTP 200, and process events idempotently. Testing this locally requires either forwarding events with the Stripe CLI or giving Stripe a public HTTPS URL that reaches your machine.

Both paths are documented below. Choose based on whether you need dashboard-level integration testing or fast handler iteration.

Start from the Test Webhooks Locally hub if you are comparing tunnel vs CLI across multiple providers.

Option 1: Stripe CLI (fastest for development)

For day-to-day handler development, the Stripe CLI is the quickest path. It receives events from Stripe and forwards them to localhost — no public URL required.

Install and authenticate:

stripe login

Forward events to your handler:

stripe listen --forward-to localhost:3000/api/webhooks/stripe

The CLI prints a webhook signing secret:

Ready! Your webhook signing secret is whsec_xxxxxxxx

Use this whsec_ value as STRIPE_WEBHOOK_SECRET in your local environment.

Trigger test events:

stripe trigger payment_intent.succeeded

When to use the CLI:

Option 2: LocalToLink (full HTTPS URL testing)

Use a tunnel when you need to test with the real Stripe Dashboard webhook endpoint — for example, verifying your production-like URL format, testing Stripe's "Send test webhook" button, or validating webhook endpoint configuration before deploy.

1. Start your app:

npm run dev

2. Start the tunnel:

npx localtolink --port 3000

3. Register in Stripe Dashboard → Developers → Webhooks → Add endpoint:

FieldValue
Endpoint URLhttps://k7m2x9p4q1.localtolink.com/api/webhooks/stripe
EventsSelect the events your handler processes
API versionMatch your production version

Replace k7m2x9p4q1 with the 10-character subdomain LocalToLink prints.

4. Copy the signing secret from the endpoint details page (whsec_...) into your local environment.

When to use a tunnel:

Verify webhook signatures

Your handler must verify Stripe signatures regardless of approach. Never process unverified payloads.

import Stripe from "stripe";

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function POST(req) {
  const body = await req.text();
  const sig = req.headers.get("stripe-signature");

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return new Response(`Webhook Error: ${err.message}`, { status: 400 });
  }

  // Handle event.type
  return new Response("ok", { status: 200 });
}

Signing secret source:

ApproachSecret comes from
Stripe CLIOutput of stripe listen (whsec_...)
Dashboard endpointEndpoint details in Stripe Dashboard

Use the correct secret for the approach you are testing. Mixing them causes verification failures.

Handler best practices

Important notes for LocalToLink

Troubleshooting

ProblemFix
Webhook signature verification failedConfirm STRIPE_WEBHOOK_SECRET matches the source (CLI vs Dashboard)
Dashboard shows failed deliveryCheck handler returns 200; inspect local server logs
No events receivedVerify tunnel is running; confirm URL matches subdomain
Session endedRestart tunnel; update Dashboard endpoint URL
Handler receives events but crashesCheck event types you subscribed to match handler cases

CLI vs tunnel decision matrix

NeedStripe CLILocalToLink tunnel
Fast handler iteration✓
Dashboard "Send test webhook"✓
Exact production URL format✓
No account / no public URL✓✓ (no LocalToLink account)
Request inspector UIUse Stripe Dashboard Events

FAQ

Can I use both CLI and tunnel?
Yes, but use separate signing secrets. Do not mix CLI secrets with Dashboard endpoint secrets.

Does Stripe support localhost URLs?
Stripe Dashboard requires HTTPS endpoints reachable from the internet. http://localhost works only with the CLI forwarder, not Dashboard registration.

What about Stripe Connect webhooks?
Same pattern — register the tunnel URL for Connect webhook endpoints in Dashboard or use CLI forwarding.

Related

Try it now

Start a tunnel from your project directory — no account required.

$ npx --yes localtolink@latest

See the full CLI reference for flags and exit codes.

LocalToLink CLI 1.0.1 — verified 2026-09-05