Home/Blog/Testing Webhooks Locally with ngrok: Complete Guide
Developer Tools

Testing Webhooks Locally with ngrok: Complete Guide

Master local webhook testing with ngrok. Learn how to expose your development server to the internet, inspect webhook payloads, and debug integrations before deploying to production.

By InventiveHQ Team

Introduction

Testing webhooks during development is a common challenge that frustrates developers worldwide. Third-party services need to send webhook events to your application, but they require a publicly accessible HTTPS endpoint—something your localhost:3000 server definitely isn't. You could deploy every code change to a staging server just to test a webhook integration, but that's slow, expensive, and impractical for rapid development.

This is where ngrok becomes indispensable. With a single command, ngrok creates a secure tunnel from the internet directly to your local development server, giving you a public HTTPS URL that webhook providers can reach. You can develop locally, see webhook payloads in real-time, debug issues instantly, and iterate quickly—all without deploying a single line of code.

In this comprehensive guide, we'll cover everything you need to know about testing webhooks locally with ngrok, from basic setup to advanced features, alternative tools, and best practices for secure development workflows.

What is ngrok?

Ngrok is a reverse proxy tool that creates secure tunnels from public URLs to services running on your local machine. Think of it as a bridge: one end sits on the public internet with an HTTPS URL, and the other end connects to your localhost server. When someone (like a webhook provider) sends a request to the ngrok URL, it gets securely forwarded through the tunnel to your local development environment.

How ngrok Works

The architecture is straightforward:

  1. Local Agent: You run the ngrok client on your machine, specifying which local port to expose
  2. Cloud Service: The ngrok cloud service creates a public URL and establishes a secure connection to your client
  3. Tunnel: Incoming requests to the public URL are forwarded through an encrypted tunnel to your local server
  4. Response: Your local server processes the request and sends the response back through the tunnel

This happens in milliseconds, making it perfect for webhook testing where you need real-time feedback. The tunnel uses HTTP/2 and TLS encryption, ensuring that webhook payloads remain secure in transit.

Key Benefits

  • Instant public URL: No DNS configuration, no server setup required
  • HTTPS by default: Webhook providers often require SSL/TLS
  • Request inspection: Built-in web interface shows every request and response
  • No firewall changes: Works behind corporate firewalls and NAT
  • Cross-platform: Runs on Mac, Windows, Linux, and more

Installing ngrok

Getting started with ngrok takes just a few minutes. The tool is distributed as a single binary with no dependencies.

Mac Installation

The easiest method on macOS is using Homebrew:

# Install via Homebrew
brew install ngrok/ngrok/ngrok

# Verify installation
ngrok version

Alternatively, download the macOS package directly from ngrok.com/download and move the binary to your PATH:

# Download and unzip
curl -O https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-darwin-amd64.zip
unzip ngrok-v3-stable-darwin-amd64.zip

# Move to PATH
sudo mv ngrok /usr/local/bin/

# Make executable
chmod +x /usr/local/bin/ngrok

Windows Installation

For Windows, download the ZIP file from the ngrok download page, extract it, and either:

  • Run ngrok.exe directly from the extracted folder
  • Add the folder to your system PATH for command-line access
  • Use Chocolatey: choco install ngrok

Linux Installation

On Linux, download and install the appropriate package:

# Download for Linux (64-bit)
wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz

# Extract
tar xvzf ngrok-v3-stable-linux-amd64.tgz

# Move to PATH
sudo mv ngrok /usr/local/bin/

# Make executable
chmod +x /usr/local/bin/ngrok

Authentication Setup

After installation, create a free account at dashboard.ngrok.com/signup and authenticate your ngrok client:

# Add your authtoken (found in your ngrok dashboard)
ngrok config add-authtoken YOUR_AUTHTOKEN_HERE

This stores your authentication token and unlocks features like longer session times and request inspection.

Basic Usage

Starting an ngrok tunnel is remarkably simple. The most common use case is exposing a local web server.

Starting a Tunnel

If you're running a development server on port 3000:

ngrok http 3000

Ngrok will display output like this:

ngrok

Session Status                online
Account                       [email protected] (Plan: Free)
Version                       3.3.0
Region                        United States (us)
Latency                       23ms
Web Interface                 http://127.0.0.1:4040
Forwarding                    https://a1b2-3c4d-5e6f.ngrok-free.app -> http://localhost:3000

Connections                   ttl     opn     rt1     rt5     p50     p90
                              0       0       0.00    0.00    0.00    0.00

The critical piece of information here is the Forwarding line: https://a1b2-3c4d-5e6f.ngrok-free.app is your public URL. Any HTTP or HTTPS request to that URL gets forwarded to your localhost:3000.

Using the Public URL

Copy the ngrok URL (e.g., https://a1b2-3c4d-5e6f.ngrok-free.app) and configure it as the webhook endpoint in your third-party service. For example:

  • Stripe: Set it as your webhook endpoint at https://dashboard.stripe.com/webhooks
  • GitHub: Configure it as the payload URL in repository settings
  • Twilio: Use it as the webhook URL for incoming messages

When the service sends a webhook, it will arrive at your local development server within milliseconds.

Web Interface

Ngrok includes a powerful web interface at http://127.0.0.1:4040. Open this in your browser while ngrok is running to see:

  • Request history: Every request with full headers and body
  • Response data: Status codes, headers, response bodies
  • Timing information: How long your server took to respond
  • Replay functionality: Resend any request with one click

This interface is invaluable for debugging webhook integrations. You can see exactly what payload a service sent, inspect headers for authentication signatures, and test your handler code repeatedly without triggering new webhooks from the source.

Advanced Features

Beyond basic tunneling, ngrok offers powerful features that streamline webhook development.

Custom Subdomains

Free ngrok accounts get random URLs that change every restart (https://a1b2-3c4d-5e6f.ngrok-free.app). Paid accounts can reserve custom subdomains:

# Use a reserved subdomain (requires paid plan)
ngrok http --domain=myapp.ngrok.io 3000

This gives you a consistent URL like https://myapp.ngrok.io, which is essential if you're working with services that don't allow frequent webhook URL updates.

Authentication

Protect your tunnel with basic authentication to prevent unauthorized access:

# Require username and password
ngrok http 3000 --basic-auth="username:password"

Now anyone accessing your ngrok URL must provide credentials. This is especially important if you're testing with sensitive data or need to share a tunnel URL with teammates.

For OAuth-based authentication:

# Require Google OAuth (paid plans)
ngrok http 3000 --oauth=google [email protected]

Request Inspection

The web interface at http://127.0.0.1:4040 is the primary inspection tool, but you can also use ngrok's API to programmatically access request data:

# Get request details via API
curl http://127.0.0.1:4040/api/requests

This returns JSON with all captured requests, useful for automated testing or logging.

Replay Functionality

One of ngrok's most powerful features is request replay. When debugging a webhook handler, you can:

  1. Receive a webhook from the provider
  2. See it fail in your logs
  3. Fix your code
  4. Click "Replay" in the ngrok web interface
  5. See the same exact request hit your server again

This eliminates the need to trigger new webhooks from the source, which might have rate limits, require manual actions, or only fire on specific events.

Traffic Filtering

Filter which requests appear in the inspector:

# Only show POST requests
ngrok http 3000 --inspect=POST

# Filter by path
ngrok http 3000 --inspect=/webhook

This reduces noise when you're only interested in specific webhook endpoints.

Testing Workflow

Here's a complete workflow for testing webhooks locally using ngrok.

Step 1: Start Your Local Server

First, run your application's development server. Here are examples in common frameworks:

Express.js (Node.js)

const express = require('express');
const app = express();

app.use(express.json());

app.post('/webhook', (req, res) => {
  console.log('Received webhook:', req.body);

  // Process webhook payload
  const { event, data } = req.body;

  // Your business logic here
  console.log(`Processing ${event} event`);

  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Server running on port 3000');
});

Flask (Python)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhook', methods=['POST'])
def webhook():
    payload = request.get_json()
    print(f"Received webhook: {payload}")

    # Process webhook payload
    event = payload.get('event')
    data = payload.get('data')

    # Your business logic here
    print(f"Processing {event} event")

    return jsonify({'received': True}), 200

if __name__ == '__main__':
    app.run(port=3000, debug=True)

PHP (with built-in server)

<?php
// webhook.php

// Read raw POST data
$payload = file_get_contents('php://input');
$data = json_decode($payload, true);

// Log the webhook
error_log("Received webhook: " . print_r($data, true));

// Process webhook payload
$event = $data['event'] ?? '';
$webhookData = $data['data'] ?? [];

// Your business logic here
error_log("Processing {$event} event");

// Send response
header('Content-Type: application/json');
echo json_encode(['received' => true]);
?>

Run the PHP server:

php -S localhost:3000

Step 2: Start ngrok Tunnel

With your server running, start ngrok:

ngrok http 3000

Copy the HTTPS forwarding URL from the output.

Step 3: Configure Webhook Provider

Go to your webhook provider's dashboard and configure the webhook endpoint. For example:

  • Stripe: https://your-ngrok-url.ngrok-free.app/webhook
  • GitHub: https://your-ngrok-url.ngrok-free.app/webhook
  • Twilio: https://your-ngrok-url.ngrok-free.app/webhook

Most providers let you select which events to subscribe to and test the webhook immediately.

Step 4: Test and Inspect

Trigger a test webhook from the provider. You should see:

  1. The request appear in your server logs
  2. The request show up in ngrok's web interface (http://127.0.0.1:4040)
  3. Your response sent back to the provider

Use the ngrok web interface to inspect:

  • Request headers (especially authentication headers like X-Signature)
  • Request body (the webhook payload)
  • Your server's response
  • Response time

Step 5: Debug and Iterate

If something goes wrong:

  1. Check your server logs for errors
  2. Inspect the webhook payload in ngrok's interface
  3. Fix your code
  4. Click "Replay" in ngrok to resend the exact same request
  5. Repeat until it works

This workflow lets you iterate rapidly without waiting for new webhooks from the provider.

Step 6: Verify Webhook Signatures

Most webhook providers sign their payloads to prove authenticity. Verify these signatures in your handler:

Stripe Example (Node.js)

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    console.log(`Webhook signature verification failed: ${err.message}`);
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  // Handle the verified event
  console.log('Verified webhook:', event.type);
  res.json({ received: true });
});

GitHub Example (Node.js)

const crypto = require('crypto');

app.post('/webhook', (req, res) => {
  const signature = req.headers['x-hub-signature-256'];
  const secret = process.env.GITHUB_WEBHOOK_SECRET;

  const hmac = crypto.createHmac('sha256', secret);
  const digest = 'sha256=' + hmac.update(JSON.stringify(req.body)).digest('hex');

  if (signature !== digest) {
    return res.status(401).send('Invalid signature');
  }

  console.log('Verified GitHub webhook:', req.body);
  res.json({ received: true });
});

Alternative Tools

While ngrok is the most popular solution, several alternatives exist depending on your needs.

Cloudflare Tunnel

Cloudflare Tunnel (formerly Argo Tunnel) is a free, production-grade alternative that connects your local server to Cloudflare's global network.

Setup:

# Install cloudflared
brew install cloudflare/cloudflare/cloudflared

# Login
cloudflared tunnel login

# Create a tunnel
cloudflared tunnel create my-webhook-tunnel

# Run the tunnel
cloudflared tunnel --url http://localhost:3000

Pros:

  • Free for unlimited use
  • Production-grade performance and reliability
  • DDoS protection from Cloudflare's network
  • Can use custom domains

Cons:

  • More complex setup than ngrok
  • No built-in request inspection interface
  • Requires Cloudflare account

localtunnel

Localtunnel is an open-source alternative with a simpler API.

Setup:

# Install globally
npm install -g localtunnel

# Start tunnel
lt --port 3000

Pros:

  • Completely free and open source
  • Simple installation via npm
  • Supports custom subdomains

Cons:

  • Less reliable than ngrok
  • No request inspection interface
  • Occasional downtime

VS Code Port Forwarding

If you use VS Code, the built-in port forwarding feature can expose local ports.

Setup:

  1. Open Command Palette (Cmd/Ctrl + Shift + P)
  2. Type "Forward a Port"
  3. Enter your port number (3000)
  4. VS Code generates a public URL

Pros:

  • Built into VS Code—no installation needed
  • Integrated with your development environment
  • Free with GitHub account

Cons:

  • Requires VS Code and GitHub authentication
  • Limited control compared to ngrok
  • No advanced features like replay

serveo

Serveo uses SSH to create tunnels without installing software.

Setup:

ssh -R 80:localhost:3000 serveo.net

Pros:

  • No installation required (uses SSH)
  • Simple command-line interface

Cons:

  • Can be unreliable
  • No web interface for inspection
  • Limited features

Choosing the Right Tool

  • Development and testing: ngrok (best inspection tools)
  • Production or staging: Cloudflare Tunnel (free, reliable)
  • VS Code users: VS Code Port Forwarding (integrated)
  • Quick tests: localtunnel or serveo (fast setup)

Webhook Payload Generator Integration

Testing webhooks effectively requires realistic test data. Our Webhook Payload Generator tool helps you create properly formatted webhook payloads for popular services without triggering real events.

How to Use It with ngrok

  1. Start your local server and ngrok tunnel
  2. Generate a test payload using the Webhook Payload Generator
    • Select your service (Stripe, GitHub, Shopify, etc.)
    • Choose the event type
    • Customize field values if needed
  3. Send the payload to your ngrok URL using curl or the generator's built-in testing feature
  4. Inspect the request in ngrok's web interface
  5. Debug your handler based on the response

Example Workflow

Generate a Stripe payment webhook:

# Generate payload with the tool, then send it:
curl -X POST https://your-ngrok-url.ngrok-free.app/webhook \
  -H "Content-Type: application/json" \
  -H "Stripe-Signature: mock_signature_for_testing" \
  -d '{
    "id": "evt_test_123",
    "object": "event",
    "type": "payment_intent.succeeded",
    "data": {
      "object": {
        "id": "pi_test_456",
        "amount": 2000,
        "currency": "usd",
        "status": "succeeded"
      }
    }
  }'

The Webhook Payload Generator eliminates the need to manually craft JSON payloads or trigger real events during development, saving significant time in your testing workflow.

Troubleshooting

Here are five common issues when testing webhooks locally and their solutions.

1. "Failed to complete tunnel connection"

Cause: Network or firewall blocking ngrok's connection to its cloud service.

Solution:

  • Check if your firewall is blocking ngrok
  • Try a different network (e.g., mobile hotspot)
  • Ensure ngrok isn't blocked by corporate firewall
  • Run ngrok diagnose to check connectivity

2. Webhook provider can't reach ngrok URL

Cause: Webhook provider's IP might be blocked, or ngrok free tier limits exceeded.

Solution:

  • Verify the URL is correct (copy from ngrok output)
  • Check if you've exceeded free tier limits (restart ngrok)
  • Some providers require HTTPS (ngrok provides this by default)
  • Ensure your local server is actually running and responding

3. "Invalid signature" errors

Cause: Webhook signature verification failing because the secret doesn't match.

Solution:

  • Get the webhook signing secret from your provider's dashboard
  • Store it in your environment variables
  • Use the correct secret in your verification code
  • Check that you're using the raw request body for signature verification
  • Some frameworks parse JSON automatically—use express.raw() for signature endpoints

4. Ngrok URL changes every restart

Cause: Free ngrok accounts get random URLs on each restart.

Solution:

  • Upgrade to a paid plan ($8/month) for reserved domains
  • Use ngrok's config file to set preferences:
# ~/.ngrok2/ngrok.yml
version: "2"
authtoken: YOUR_AUTHTOKEN
tunnels:
  webhook:
    proto: http
    addr: 3000
    domain: myapp.ngrok.io  # Paid plan only

Then run: ngrok start webhook

5. Slow webhook delivery

Cause: Network latency between webhook provider, ngrok's cloud, and your local machine.

Solution:

  • Choose the nearest ngrok region:
ngrok http 3000 --region us  # or eu, ap, au, sa, jp, in
  • Check your internet connection speed
  • For production, use proper hosting instead of tunnels

Best Practices

Follow these practices for secure and efficient webhook testing.

Security

  1. Never commit ngrok URLs to version control: URLs are temporary and expose your local environment
  2. Use authentication on sensitive tunnels: ngrok http 3000 --basic-auth="user:pass"
  3. Rotate webhook secrets regularly: Store them in environment variables, not code
  4. Validate webhook signatures: Always verify authenticity before processing
  5. Use HTTPS: ngrok provides it by default—use the HTTPS URL
  6. Limit tunnel exposure: Stop ngrok when not actively testing
  7. Monitor the web interface: Watch for unexpected requests

Performance

  1. Choose the nearest region: Reduces latency
  2. Use reserved domains (paid plans): Eliminates configuration changes between sessions
  3. Test with realistic payloads: Use the Webhook Payload Generator for accurate data
  4. Log everything: Your application should log all webhook events for debugging
  5. Use replay: Avoid triggering real webhooks repeatedly—replay in ngrok instead

Debugging

  1. Always open ngrok's web interface: Essential for seeing what actually arrived
  2. Compare payloads: Check what you received vs. the provider's documentation
  3. Test signature verification: Use test mode secrets from the provider
  4. Check response status: Providers often retry on non-200 responses
  5. Use verbose logging: Log headers, body, and processing steps
  6. Test error cases: Use the Webhook Payload Generator to create edge cases

Team Collaboration

  1. Share inspection URLs: Ngrok lets teammates view the web interface
  2. Document webhook endpoints: Keep a list of which ngrok URLs are used for what
  3. Use consistent ports: Makes it easier to share ngrok commands
  4. Consider paid plans: Reserved domains and more simultaneous tunnels

Conclusion

Testing webhooks locally with ngrok transforms webhook development from a frustrating deployment cycle into a smooth, rapid iteration process. With a single command, you get a public HTTPS endpoint, real-time request inspection, and the ability to test and debug webhook integrations without leaving your local environment.

Key takeaways:

  • Start simple: Install ngrok, run ngrok http 3000, configure the webhook URL
  • Use the web interface: http://127.0.0.1:4040 is your debugging command center
  • Leverage replay: Fix code and resend requests without triggering new webhooks
  • Generate test payloads: Use our Webhook Payload Generator for realistic test data
  • Verify signatures: Always validate webhook authenticity in your handlers
  • Consider alternatives: Cloudflare Tunnel for production, localtunnel for quick tests

Moving to Production

When you're ready to deploy, remember that ngrok is a development tool. For production webhooks:

  1. Deploy to proper hosting: Use Vercel, AWS, Cloudflare Pages, or similar
  2. Configure custom domains: Real domain names, not random subdomains
  3. Set up SSL certificates: Let's Encrypt or your provider's SSL
  4. Implement monitoring: Track webhook delivery, failures, and retries
  5. Add retry logic: Handle temporary failures gracefully
  6. Use webhook queues: Process webhooks asynchronously for reliability
  7. Keep testing locally: Continue using ngrok for new webhook integrations

Start testing your webhooks locally today with ngrok, and use our Webhook Payload Generator tool to create realistic test payloads. Your development workflow will thank you.

Ready to generate test webhook payloads? Try our free Webhook Payload Generator to create properly formatted webhook data for Stripe, GitHub, Shopify, and more—no account required.

Need Expert IT & Security Guidance?

Our team is ready to help protect and optimize your business technology infrastructure.