Hookbase
LoginGet Started Free
Back to Blog
Tutorial

How to Receive Stripe Webhooks on Localhost (Three Ways)

Stripe CLI, ngrok, and Hookbase tunnel — the three options for testing Stripe webhooks against your local dev server, with the tradeoffs that matter when you actually use them.

Hookbase Team
April 12, 2026
5 min read

The Setup

You're building a Stripe integration. You need to test how your handler reacts to a real payment_intent.succeeded event. Your dev server is on localhost:4000. Stripe needs a public URL.

Three options. They differ less in capability than in daily friction.

Option 1: Stripe CLI

Stripe ships an official CLI that listens for events on your account and forwards them to your local handler:

brew install stripe/stripe-cli/stripe
stripe login
stripe listen --forward-to localhost:4000/webhook

The CLI prints a temporary signing secret. Use it as your STRIPE_WEBHOOK_SECRET while developing — it's different from your production webhook secret.

Pros: Official. Free. Triggers test events from the CLI (stripe trigger payment_intent.succeeded). No URL configuration in the Stripe dashboard.

Cons: Only works with Stripe. The signing secret rotates every stripe listen session. Forwards events from your entire account to your laptop, which is fine in test mode and a bit alarming in live mode.

This is the right answer if you only need to test Stripe and don't mind running the CLI alongside your dev server.

Option 2: ngrok

The classic. Works with any provider, not just Stripe:

ngrok http 4000

Take the https://abc123.ngrok-free.app URL it gives you, paste it into the Stripe dashboard webhook config, and you're live.

Pros: Works with every webhook provider, not Stripe-specific.

Cons: Free URLs change every restart. You'll be re-pasting URLs into the Stripe dashboard several times a day. Stable subdomains require a paid plan.

Option 3: Hookbase Tunnel

npm install -g @hookbase/cli
hookbase login
hookbase tunnel --port 4000

You get a stable URL like https://acme.hookbase.app/ingest/stripe-dev. Configure that once in the Stripe dashboard and never touch it again.

Pros: Stable URL. Works with every provider, not just Stripe. Every event is stored, viewable, and replayable from the dashboard. Signature verification happens at the tunnel — your handler doesn't need to verify the Stripe HMAC at all if you don't want to.

Cons: Requires a Hookbase account (free tier covers all dev usage).

Comparison At A Glance

| | Stripe CLI | ngrok (free) | Hookbase | |---|---|---|---| | URL changes per session | Pseudo (uses ephemeral secret) | Yes | No | | Multi-provider | No | Yes | Yes | | Event history | No | No | Yes | | Replay past events | Limited (stripe events resend) | No | Yes | | Signature verification | In your handler | In your handler | At tunnel (optional) | | Cost | Free | Free / paid for stable URL | Free |

The Stripe-Specific Setup Steps

Whichever tunnel you pick, the dashboard steps are the same:

  1. Stripe Dashboard → Developers → Webhooks → Add endpoint
  2. Endpoint URL: your tunnel URL
  3. Events: pick the ones you care about. payment_intent.succeeded, charge.refunded, customer.subscription.updated are the typical starter set.
  4. After saving, click into the webhook and reveal the Signing secret (whsec_...). This is what your handler uses.
  5. Trigger a test event from the dashboard or with stripe trigger.

For test mode, use a webhook in your test-mode dashboard and a test-mode signing secret. Live mode webhooks have a separate secret that won't validate test events.

Verifying the Signature

Stripe's signature header is Stripe-Signature and looks like:

t=1700000000,v1=8e3a7f2c...

You parse it, extract the timestamp and v1 hash, then HMAC-SHA256 the string {timestamp}.{raw_body} with your signing secret:

import Stripe from 'stripe';

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

app.post(
  '/webhook',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    let event;
    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        req.header('Stripe-Signature'),
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      return res.status(400).send('Webhook Error');
    }

    res.send('ok');
  }
);

stripe.webhooks.constructEvent does the parsing, hashing, timing-safe comparison, and timestamp freshness check (5-minute tolerance) for you. Don't reimplement it.

Common Issues

"Signature verification failed." Your body is being parsed before reaching the handler. Use express.raw() (or your framework's equivalent) — never express.json() for this route.

"My CLI signing secret stopped working." It rotated. Restart stripe listen and copy the new one.

"Stripe says 'webhook disabled.'" Your endpoint returned non-2xx for too long. Re-enable in the dashboard.

Beyond Local Dev

The Hookbase tunnel URL you used in dev is the same primitive used in production. When you're ready to ship, point production Stripe webhooks at a Hookbase source backed by your prod handler. You get the same event history, replay, and reliability layer in front of your live integration that you used during development.

Install the CLI or start a free account.

stripewebhookstutoriallocalhostdevelopment

Related Articles

Tutorial

Shopify Webhook Signature Verification, Explained

Shopify HMAC verification trips up almost every first-time integrator. Here is exactly how the signature is computed, what goes wrong, and a working implementation in Node, Python, Go, and Ruby.

Reference

Webhook Retries: What Every Provider Does Differently

Stripe retries for 3 days. GitHub gives up after one failure. Shopify retries 19 times. Knowing the rules for each provider is the difference between losing events and not. A reference table plus what it means for your handler.

Best Practices

Idempotency Keys for Webhooks: A Practical Guide

Webhooks get retried. Without idempotency, that means duplicate orders, double charges, and angry customers. Here is how to design a deduplication strategy that actually works.

Ready to Try Hookbase?

Start receiving, transforming, and routing webhooks in minutes.

Get Started Free
Hookbase

Reliable webhook infrastructure for modern teams. Built on Cloudflare's global edge network.

Product

  • Features
  • Pricing
  • Use Cases
  • Integrations
  • ngrok Alternative

Resources

  • Documentation
  • API Reference
  • CLI Guide
  • Blog
  • FAQ

Free Tools

  • All Tools
  • Webhook Bin
  • HMAC Calculator
  • JSONata Playground
  • Cron Builder
  • Payload Formatter
  • Local Testing

Legal

  • Privacy Policy
  • Terms of Service
  • Contact
  • Status

© 2026 Hookbase. All rights reserved.