DEV Community

Cover image for How to Receive Stripe Webhooks on Localhost (Without the Headache)
Taha Paksu
Taha Paksu

Posted on

How to Receive Stripe Webhooks on Localhost (Without the Headache)

If you've ever integrated Stripe payments, you know the drill: Stripe sends webhook events to a public URL, but your development server is running on localhost:3000. You need those events to reach your machine in real time so you can test payment flows, handle subscription changes, and debug edge cases with real payloads.

In this post, I'll walk through how to bridge that gap using Hooklink, a webhook-to-localhost tool that gives you a permanent subdomain, signature verification, event filtering, and replay capabilities out of the box.

The Problem

Stripe needs a publicly accessible HTTPS endpoint to deliver webhook events. During development, your server is behind NAT, a firewall, or simply running on localhost. The typical workarounds each have drawbacks:

  • Stripe CLI: Works, but only for Stripe. If you also test GitHub, Shopify, or Slack webhooks, you need separate tools for each.
  • ngrok: Generic tunnel, but the URL changes on every restart (free tier), there's no webhook replay, and no built-in signature verification.
  • Deploying to staging: Slow feedback loop. You lose the ability to set breakpoints and iterate quickly.

The Setup: Hooklink + Stripe in 5 Minutes

Here's the full flow, from zero to receiving live Stripe events on your machine.

Step 1: Create a Free Hooklink Account

Head to app.hooklink.net/register and create an account. The free plan includes 2,500 requests per week, which is more than enough for development.

Step 2: Create a Stripe Endpoint

In the Hooklink dashboard, go to Endpoints and click Create Endpoint. You can start from scratch, but the fastest path is to use the built-in Stripe template:

  1. Go to Integrations in the sidebar
  2. Find the Stripe card and click Use Template
  3. The dialog pre-fills everything for you:
    • Keyword: stripe (your URL becomes https://stripe-yourname.hooklink.net)
    • Local target: http://localhost:3000/api/webhooks/stripe
    • Source allowlist: All 12 official Stripe webhook IP ranges, pre-loaded
    • Event filter: Common events like payment_intent.succeeded, checkout.session.completed, invoice.paid, and more
  4. Adjust the local target URL to match your project's webhook route, then click Create

Create a Stripe webhook endpoint on hooklink

Your endpoint is now live at a permanent, memorable URL:

https://stripe-yourname.hooklink.net
Enter fullscreen mode Exit fullscreen mode

This URL never changes. No more updating Stripe's dashboard every time you restart a tunnel.

Step 3: Configure Stripe to Send Webhooks

In the Stripe Dashboard:

  1. Go to Developers → Webhooks
  2. Click Add endpoint
  3. Paste your Hooklink URL: https://stripe-yourname.hooklink.net
  4. Select the events you want to receive (e.g., payment_intent.succeeded, checkout.session.completed, customer.subscription.created)
  5. Click Add endpoint

Put hooklink url as Stripe webhook target

Step 4: (Optional) Enable Signature Verification

Stripe signs every webhook with an HMAC-SHA256 signature in the Stripe-Signature header. Hooklink can verify this signature at the edge before forwarding to your machine, blocking forged requests.

  1. In Stripe's webhook settings, copy the Signing secret (starts with whsec_)
  2. In Hooklink's endpoint settings, enable Signature Verification
  3. Select HMAC-SHA256, set the header to Stripe-Signature, and paste the signing secret

Hooklink understands Stripe's unique signature format (t=timestamp,v1=signature) natively. It parses the timestamp, constructs the signed payload as {timestamp}.{body}, and performs a constant-time comparison. It also checks the timestamp against a configurable tolerance window (default: 5 minutes) to prevent replay attacks.

Step 5: Install the CLI and Connect

Install the Hooklink CLI globally:

npm install -g @hooklink/cli
Enter fullscreen mode Exit fullscreen mode

Generate an API key from the Dashboard → API Keys page, then authenticate:

hooklink login --key hlk_your_api_key_here
Enter fullscreen mode Exit fullscreen mode

Now connect to your Stripe endpoint:

hooklink connect stripe
Enter fullscreen mode Exit fullscreen mode

You'll see output like this:

$ hooklink connect stripe

  Connection Details:
    Version:      v1.2.0
    Endpoint:     stripe
    Webhook URL:  https://stripe-yourname.hooklink.net
    Target:       http://localhost:3000/api/webhooks/stripe

  Waiting for webhooks...
Enter fullscreen mode Exit fullscreen mode

That's it. Every Stripe webhook now flows to your local server in real time over a persistent WebSocket connection.

Step 6: Trigger a Test Event

Go back to Stripe's webhook settings and click Send test webhook. Choose an event like payment_intent.succeeded. In your terminal, you'll see:

  stripe   POST --> payment_intent.succeeded
  stripe   POST <-- 200  12ms
Enter fullscreen mode Exit fullscreen mode

The CLI auto-detects Stripe event types by reading the type field from the JSON body, so you always see a human-readable label instead of a raw POST path.

Beyond the Basics

Replay Webhooks Without Re-Triggering Events

Every webhook that hits your endpoint is logged with full headers, body, and response data. Found a bug in your handler? Fix it, then replay the exact same webhook from the Hooklink dashboard. No need to trigger a new payment in Stripe.

You can even edit the payload before replaying: change the event type, modify amounts, add edge-case fields. Modified replays are flagged in the history so you can tell them apart.

Event Filtering

The Stripe template pre-configures an event filter that extracts the type field from the request body and matches it against a list of allowed values. If you only care about checkout.session.completed and invoice.paid, remove the rest. Unmatched events are dropped before they reach your machine.

Filters support AND/OR logic, so you can combine multiple conditions (e.g., only forward events where type is invoice.paid AND the livemode header is true).

Source Allowlisting

The Stripe template automatically adds all 12 of Stripe's official webhook IP ranges as CIDR rules. Any request from an IP outside those ranges gets a 403 Forbidden before it even reaches signature verification. This is defense in depth: even if someone guesses your webhook URL, they can't send forged events unless they're coming from Stripe's infrastructure.

Multi-Service Setup

Testing Stripe alongside GitHub and a frontend preview? Connect all three in one terminal:

hooklink connect stripe,github,preview
Enter fullscreen mode Exit fullscreen mode

Each endpoint routes to its own local service. Stripe webhooks go to port 3000, GitHub hooks to port 4000, your preview tunnel to port 5173. One WebSocket connection, one terminal window.

  Connected to 3 endpoints

  stripe-yourname.hooklink.net   webhook  :3000
  github-yourname.hooklink.net   webhook  :4000
  preview-yourname.hooklink.net  tunnel   :5173

  stripe   POST --> payment_intent.succeeded
  stripe   POST <-- 200  12ms
  github   POST --> push
  github   POST <-- 200   8ms
  preview  GET      /dashboard  200
Enter fullscreen mode Exit fullscreen mode

Hooklink vs. Stripe CLI vs. ngrok

Feature Stripe CLI ngrok (free) Hooklink (free)
Permanent URL No No Yes
Stripe signature verification Built-in No Built-in
Webhook replay No No Yes (with editing)
Event filtering Yes No Yes
Source IP allowlisting N/A No Yes (pre-loaded)
Works with other providers No Yes Yes
Multi-endpoint in one session No No Yes
Request logging & search Limited Dashboard (paid) Full (free)

Quick Reference

# Install
npm install -g @hooklink/cli

# Authenticate
hooklink login --key hlk_your_key

# Connect to Stripe endpoint
hooklink connect stripe

# Connect to multiple endpoints
hooklink connect stripe,github,preview

# View recent webhook logs
hooklink logs --endpoint stripe

# Check connection status
hooklink status
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

Testing Stripe webhooks locally doesn't have to be painful. With Hooklink, you get a permanent URL that survives restarts, built-in Stripe signature verification, event filtering, source allowlisting, and the ability to replay any webhook with one click.

The free plan covers 2,500 requests per week with 3 endpoints and full request logging. For most development workflows, that's more than enough.

Create a free account or try it instantly without signing up:

npx @hooklink/cli listen 3000
Enter fullscreen mode Exit fullscreen mode

You'll have a public webhook URL in under 30 seconds.

Top comments (0)