Install ngrok, run ngrok http 3000 to create a tunnel from a public URL to your local port 3000, copy the generated https URL, and paste it as your webhook URL in the Paystack dashboard (with your webhook path appended). When Paystack sends a webhook, it hits the ngrok URL, which forwards the request to your local server. Use the ngrok web inspector at localhost:4040 to see every request and response in real time.
When you are developing locally, your server runs on localhost:3000 (or whatever port you choose). Paystack cannot reach localhost. It is not a real internet address. When Paystack tries to deliver a webhook to localhost:3000, the request has nowhere to go.
ngrok solves this by creating a tunnel: a public URL on the internet that forwards traffic to your local machine. When Paystack sends a webhook to https://abc123.ngrok-free.app/webhook/paystack, ngrok receives the request on its servers, forwards it through the tunnel to your machine, and delivers it to localhost:3000/webhook/paystack. Your local server sees the request as if it came from the internet.
This is the standard workflow for developing any webhook integration, not just Paystack. Stripe, GitHub, Twilio, and every other service that sends webhooks all require the same solution during local development.
Alternatives to ngrok exist (Cloudflare Tunnel, localtunnel, Tailscale Funnel), and we cover Cloudflare Tunnel in a separate guide. ngrok is the most popular option because it is the simplest to set up and has the best developer experience.
ngrok is a single binary with no dependencies. Download it from ngrok.com/download or install it via package managers:
macOS (Homebrew):
brew install ngrok
Linux (snap):
snap install ngrok
Linux (manual):
wget https://bin.equinox.io/c/bNyj1mQVY4c/ngrok-v3-stable-linux-amd64.tgz
tar xvzf ngrok-v3-stable-linux-amd64.tgz
sudo mv ngrok /usr/local/bin/
Windows (Chocolatey):
choco install ngrok
Verify the installation:
ngrok version
You need an ngrok account (free) to use the service. Sign up at ngrok.com, then authenticate your local installation with your auth token:
ngrok config add-authtoken YOUR_AUTH_TOKEN
Find your auth token on the ngrok dashboard under "Your Authtoken." This is a one-time setup. ngrok remembers the token in its configuration file.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Make sure your local development server is running first. If your Express or Django or Laravel app runs on port 3000:
ngrok http 3000
ngrok starts the tunnel and displays output like this:
Session Status online
Account your@email.com
Version 3.x.x
Region United States (us)
Forwarding https://a1b2c3d4.ngrok-free.app -> http://localhost:3000
The HTTPS URL (something like https://a1b2c3d4.ngrok-free.app) is your public tunnel address. This is what you will give to Paystack.
If your server runs on a different port, change the number:
ngrok http 8000 # Django default
ngrok http 8080 # Common alternative
ngrok http 5000 # Flask default
Keep the ngrok process running in a separate terminal. If you close it, the tunnel shuts down and the URL stops working. On the free plan, every time you restart ngrok, you get a new random URL, which means you need to update your Paystack dashboard each time.
With the tunnel running, configure Paystack to send webhooks to your ngrok URL:
https://a1b2c3d4.ngrok-free.app/webhook/paystackNow trigger a test transaction. The simplest way is to use the Paystack Popup on a test page with your test public key. When the transaction completes, Paystack sends a charge.success webhook to your ngrok URL, which forwards it to your local server.
A common mistake: forgetting to append your webhook path. If your Express route is app.post('/webhook/paystack', ...), the full URL must be https://a1b2c3d4.ngrok-free.app/webhook/paystack, not just https://a1b2c3d4.ngrok-free.app. Without the path, ngrok delivers the request to your root route, which probably returns a 404 or your homepage.
Another common mistake: using your live Paystack keys during development. Always use test mode. Test transactions do not move real money, and test webhooks behave identically to live webhooks except for the data being simulated.
One of ngrok's best features is the web inspector. While your tunnel is running, open http://localhost:4040 in your browser. This dashboard shows every HTTP request that passes through the tunnel in real time.
For each request, you can see:
The inspector is your primary debugging tool during webhook development. When something is not working, open the inspector and check:
The inspector also has a "Replay" button that resends any captured request to your local server. This is invaluable for debugging: trigger a webhook once, then replay it as many times as you need while fixing your handler code. No need to create a new test transaction each time.
ngrok offers a free tier and several paid plans. Here is what matters for Paystack webhook development:
Free plan:
Paid plan (starting at $8/month):
For Paystack webhook development, the free plan works fine. The main inconvenience is updating your Paystack dashboard webhook URL every time you restart ngrok. If you develop webhooks frequently and this becomes annoying, the paid plan pays for itself in saved time.
On the free plan, ngrok adds a warning page for browser requests. This does not affect Paystack webhooks because Paystack sends HTTP POST requests programmatically, not through a browser. The interstitial page is only shown to browser-initiated requests.
If you want a free alternative with stable URLs, consider Cloudflare Tunnel, which offers free tunnels with custom subdomains under your own domain.
These are the problems developers hit most often when testing Paystack webhooks with ngrok:
1. Signature verification fails. Your handler rejects the webhook with a 401 error. This usually means your handler is using the live secret key but your Paystack dashboard is in test mode (or vice versa). The test mode uses a different secret key than live mode. Make sure your application's PAYSTACK_SECRET_KEY environment variable matches the mode you are using in the dashboard.
2. Webhooks are not arriving. You triggered a test transaction but nothing shows up in ngrok's inspector. Check: Is the ngrok tunnel still running? Is the webhook URL in the Paystack dashboard correct (including the path)? Are you in test mode in the dashboard? Did the transaction actually complete (check the Paystack dashboard for the transaction status)?
3. Your server returns 404. The request arrives at ngrok (you can see it in the inspector) but your server returns 404. This means the webhook path in your Paystack dashboard does not match the route in your application. Compare the path in the ngrok URL against your route definition.
4. Connection refused. ngrok says "connection refused" in the terminal. Your local server is not running, or it is running on a different port than what you told ngrok. Start your server first, then start ngrok with the correct port.
5. ngrok URL changed and webhooks stopped. You restarted ngrok (free plan) and got a new URL, but the Paystack dashboard still has the old URL. Update the webhook URL in the dashboard every time you restart ngrok. This is the biggest nuisance of the free plan.
6. Raw body parsing issues. Your handler receives the request but the JSON body is empty or malformed. This usually means your Express/framework middleware is parsing the body before your handler can access the raw bytes. For signature verification, you need the raw request body. See The Raw Body Problem for the fix.
Instead of typing command-line flags every time, you can create an ngrok configuration file that stores your tunnel settings:
# ~/.config/ngrok/ngrok.yml (Linux/macOS)
version: "3"
authtoken: YOUR_AUTH_TOKEN
tunnels:
paystack-dev:
proto: http
addr: 3000
inspect: true
Start the named tunnel:
ngrok start paystack-dev
If you are on a paid plan with a custom domain, add it to the config:
tunnels:
paystack-dev:
proto: http
addr: 3000
hostname: paystack-dev.ngrok.io
inspect: true
The configuration file is especially useful if you work on multiple projects with different port numbers. Define a tunnel for each project and start the one you need.
ngrok exposes your local machine to the internet. While the tunnel is running, anyone who knows the URL can send requests to your local server. Keep these security points in mind:
On paid plans, ngrok offers IP restrictions that let you limit which IP addresses can reach your tunnel. You can restrict it to Paystack's webhook IPs if you know them, adding an extra layer of security. See Webhook IP Allowlisting for details on Paystack's IP ranges.
Here is the end-to-end workflow for testing Paystack webhooks locally with ngrok:
npm run dev (or whatever your start command is). Verify it is running by visiting localhost:3000 in your browser.ngrok http 3000. Copy the HTTPS URL from the output.This workflow should become second nature. Every developer building a Paystack integration goes through this cycle dozens of times during development. The faster you can iterate through it, the faster you ship a reliable webhook handler.
KES 2,999
The definitive guide to building payment systems with Paystack — from your first transaction to production-grade payment infrastructure across Africa. 9 modules, 47 lessons. Free preview available.
Start Paystack Course