<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Taha Paksu</title>
    <description>The latest articles on DEV Community by Taha Paksu (@tpaksu).</description>
    <link>https://dev.to/tpaksu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F36932%2Fdca6553e-1733-4259-b5eb-2790bdb0103f.jpg</url>
      <title>DEV Community: Taha Paksu</title>
      <link>https://dev.to/tpaksu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tpaksu"/>
    <language>en</language>
    <item>
      <title>How to Receive Stripe Webhooks on Localhost (Without the Headache)</title>
      <dc:creator>Taha Paksu</dc:creator>
      <pubDate>Sun, 22 Feb 2026 20:41:15 +0000</pubDate>
      <link>https://dev.to/tpaksu/how-to-receive-stripe-webhooks-on-localhost-without-the-headache-4mi1</link>
      <guid>https://dev.to/tpaksu/how-to-receive-stripe-webhooks-on-localhost-without-the-headache-4mi1</guid>
      <description>&lt;p&gt;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 &lt;code&gt;localhost:3000&lt;/code&gt;. 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.&lt;/p&gt;

&lt;p&gt;In this post, I'll walk through how to bridge that gap using &lt;a href="https://hooklink.net" rel="noopener noreferrer"&gt;Hooklink&lt;/a&gt;, a webhook-to-localhost tool that gives you a permanent subdomain, signature verification, event filtering, and replay capabilities out of the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

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

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

&lt;h2&gt;
  
  
  The Setup: Hooklink + Stripe in 5 Minutes
&lt;/h2&gt;

&lt;p&gt;Here's the full flow, from zero to receiving live Stripe events on your machine.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Create a Free Hooklink Account
&lt;/h3&gt;

&lt;p&gt;Head to &lt;a href="https://app.hooklink.net/register" rel="noopener noreferrer"&gt;app.hooklink.net/register&lt;/a&gt; and create an account. The free plan includes 2,500 requests per week, which is more than enough for development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Create a Stripe Endpoint
&lt;/h3&gt;

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

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Integrations&lt;/strong&gt; in the sidebar&lt;/li&gt;
&lt;li&gt;Find the &lt;strong&gt;Stripe&lt;/strong&gt; card and click &lt;strong&gt;Use Template&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The dialog pre-fills everything for you:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keyword&lt;/strong&gt;: &lt;code&gt;stripe&lt;/code&gt; (your URL becomes &lt;code&gt;https://stripe-yourname.hooklink.net&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local target&lt;/strong&gt;: &lt;code&gt;http://localhost:3000/api/webhooks/stripe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source allowlist&lt;/strong&gt;: All 12 official Stripe webhook IP ranges, pre-loaded&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Event filter&lt;/strong&gt;: Common events like &lt;code&gt;payment_intent.succeeded&lt;/code&gt;, &lt;code&gt;checkout.session.completed&lt;/code&gt;, &lt;code&gt;invoice.paid&lt;/code&gt;, and more&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Adjust the local target URL to match your project's webhook route, then click &lt;strong&gt;Create&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcyof440jtzsfvt936c0u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcyof440jtzsfvt936c0u.png" alt="Create a Stripe webhook endpoint on hooklink" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Your endpoint is now live at a permanent, memorable URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://stripe-yourname.hooklink.net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This URL never changes. No more updating Stripe's dashboard every time you restart a tunnel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Configure Stripe to Send Webhooks
&lt;/h3&gt;

&lt;p&gt;In the &lt;a href="https://dashboard.stripe.com/webhooks" rel="noopener noreferrer"&gt;Stripe Dashboard&lt;/a&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Developers → Webhooks&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add endpoint&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Paste your Hooklink URL: &lt;code&gt;https://stripe-yourname.hooklink.net&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Select the events you want to receive (e.g., &lt;code&gt;payment_intent.succeeded&lt;/code&gt;, &lt;code&gt;checkout.session.completed&lt;/code&gt;, &lt;code&gt;customer.subscription.created&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Add endpoint&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6chz7lu08wz0fzyz1j4t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6chz7lu08wz0fzyz1j4t.png" alt="Put hooklink url as Stripe webhook target" width="800" height="431"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: (Optional) Enable Signature Verification
&lt;/h3&gt;

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

&lt;ol&gt;
&lt;li&gt;In Stripe's webhook settings, copy the &lt;strong&gt;Signing secret&lt;/strong&gt; (starts with &lt;code&gt;whsec_&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;In Hooklink's endpoint settings, enable &lt;strong&gt;Signature Verification&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;HMAC-SHA256&lt;/strong&gt;, set the header to &lt;code&gt;Stripe-Signature&lt;/code&gt;, and paste the signing secret&lt;/li&gt;
&lt;/ol&gt;

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

&lt;h3&gt;
  
  
  Step 5: Install the CLI and Connect
&lt;/h3&gt;

&lt;p&gt;Install the Hooklink CLI globally:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @hooklink/cli
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Generate an API key from the &lt;a href="https://app.hooklink.net/dashboard/keys" rel="noopener noreferrer"&gt;Dashboard → API Keys&lt;/a&gt; page, then authenticate:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hooklink login &lt;span class="nt"&gt;--key&lt;/span&gt; hlk_your_api_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now connect to your Stripe endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hooklink connect stripe
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ 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...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Every Stripe webhook now flows to your local server in real time over a persistent WebSocket connection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Trigger a Test Event
&lt;/h3&gt;

&lt;p&gt;Go back to Stripe's webhook settings and click &lt;strong&gt;Send test webhook&lt;/strong&gt;. Choose an event like &lt;code&gt;payment_intent.succeeded&lt;/code&gt;. In your terminal, you'll see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  stripe   POST --&amp;gt; payment_intent.succeeded
  stripe   POST &amp;lt;-- 200  12ms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;h2&gt;
  
  
  Beyond the Basics
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Replay Webhooks Without Re-Triggering Events
&lt;/h3&gt;

&lt;p&gt;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.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Event Filtering
&lt;/h3&gt;

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

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

&lt;h3&gt;
  
  
  Source Allowlisting
&lt;/h3&gt;

&lt;p&gt;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 &lt;code&gt;403 Forbidden&lt;/code&gt; 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.&lt;/p&gt;

&lt;h3&gt;
  
  
  Multi-Service Setup
&lt;/h3&gt;

&lt;p&gt;Testing Stripe alongside GitHub and a frontend preview? Connect all three in one terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;hooklink connect stripe,github,preview
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  Connected to 3 endpoints

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

  stripe   POST --&amp;gt; payment_intent.succeeded
  stripe   POST &amp;lt;-- 200  12ms
  github   POST --&amp;gt; push
  github   POST &amp;lt;-- 200   8ms
  preview  GET      /dashboard  200
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Hooklink vs. Stripe CLI vs. ngrok
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Feature&lt;/th&gt;
&lt;th&gt;Stripe CLI&lt;/th&gt;
&lt;th&gt;ngrok (free)&lt;/th&gt;
&lt;th&gt;Hooklink (free)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Permanent URL&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe signature verification&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Built-in&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Webhook replay&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (with editing)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event filtering&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source IP allowlisting&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes (pre-loaded)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Works with other providers&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-endpoint in one session&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request logging &amp;amp; search&lt;/td&gt;
&lt;td&gt;Limited&lt;/td&gt;
&lt;td&gt;Dashboard (paid)&lt;/td&gt;
&lt;td&gt;Full (free)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Quick Reference
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Install&lt;/span&gt;
npm &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-g&lt;/span&gt; @hooklink/cli

&lt;span class="c"&gt;# Authenticate&lt;/span&gt;
hooklink login &lt;span class="nt"&gt;--key&lt;/span&gt; hlk_your_key

&lt;span class="c"&gt;# Connect to Stripe endpoint&lt;/span&gt;
hooklink connect stripe

&lt;span class="c"&gt;# Connect to multiple endpoints&lt;/span&gt;
hooklink connect stripe,github,preview

&lt;span class="c"&gt;# View recent webhook logs&lt;/span&gt;
hooklink logs &lt;span class="nt"&gt;--endpoint&lt;/span&gt; stripe

&lt;span class="c"&gt;# Check connection status&lt;/span&gt;
hooklink status
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://app.hooklink.net/register" rel="noopener noreferrer"&gt;Create a free account&lt;/a&gt; or try it instantly without signing up:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx @hooklink/cli listen 3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll have a public webhook URL in under 30 seconds.&lt;/p&gt;

</description>
      <category>stripe</category>
      <category>webhooks</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
