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:
- Local Agent: You run the ngrok client on your machine, specifying which local port to expose
- Cloud Service: The ngrok cloud service creates a public URL and establishes a secure connection to your client
- Tunnel: Incoming requests to the public URL are forwarded through an encrypted tunnel to your local server
- 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.exedirectly 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:
- Receive a webhook from the provider
- See it fail in your logs
- Fix your code
- Click "Replay" in the ngrok web interface
- 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:
- The request appear in your server logs
- The request show up in ngrok's web interface (
http://127.0.0.1:4040) - 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:
- Check your server logs for errors
- Inspect the webhook payload in ngrok's interface
- Fix your code
- Click "Replay" in ngrok to resend the exact same request
- 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:
- Open Command Palette (
Cmd/Ctrl + Shift + P) - Type "Forward a Port"
- Enter your port number (3000)
- 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
- Start your local server and ngrok tunnel
- 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
- Send the payload to your ngrok URL using curl or the generator's built-in testing feature
- Inspect the request in ngrok's web interface
- 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 diagnoseto 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
- Never commit ngrok URLs to version control: URLs are temporary and expose your local environment
- Use authentication on sensitive tunnels:
ngrok http 3000 --basic-auth="user:pass" - Rotate webhook secrets regularly: Store them in environment variables, not code
- Validate webhook signatures: Always verify authenticity before processing
- Use HTTPS: ngrok provides it by default—use the HTTPS URL
- Limit tunnel exposure: Stop ngrok when not actively testing
- Monitor the web interface: Watch for unexpected requests
Performance
- Choose the nearest region: Reduces latency
- Use reserved domains (paid plans): Eliminates configuration changes between sessions
- Test with realistic payloads: Use the Webhook Payload Generator for accurate data
- Log everything: Your application should log all webhook events for debugging
- Use replay: Avoid triggering real webhooks repeatedly—replay in ngrok instead
Debugging
- Always open ngrok's web interface: Essential for seeing what actually arrived
- Compare payloads: Check what you received vs. the provider's documentation
- Test signature verification: Use test mode secrets from the provider
- Check response status: Providers often retry on non-200 responses
- Use verbose logging: Log headers, body, and processing steps
- Test error cases: Use the Webhook Payload Generator to create edge cases
Team Collaboration
- Share inspection URLs: Ngrok lets teammates view the web interface
- Document webhook endpoints: Keep a list of which ngrok URLs are used for what
- Use consistent ports: Makes it easier to share ngrok commands
- 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:4040is 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:
- Deploy to proper hosting: Use Vercel, AWS, Cloudflare Pages, or similar
- Configure custom domains: Real domain names, not random subdomains
- Set up SSL certificates: Let's Encrypt or your provider's SSL
- Implement monitoring: Track webhook delivery, failures, and retries
- Add retry logic: Handle temporary failures gracefully
- Use webhook queues: Process webhooks asynchronously for reliability
- 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.
