Webhook Testing
Test webhooks locally using Paystack CLI's built-in ngrok integration.
Overview
Webhook testing typically requires deploying your application to a public server. Paystack CLI makes this easy by:
- Creating an ngrok tunnel to your local server
- Automatically updating your Paystack webhook URL
- Forwarding all webhook events to your local endpoint
Prerequisites
1. Get Ngrok Auth Token
Sign up at ngrok.com and get your auth token from dashboard.ngrok.com/get-started/your-authtoken
2. Configure the Token
paystack-cli config
# Select "Ngrok Auth Token"
# Paste your tokenOr set it via environment variable:
export NGROK_AUTH_TOKEN="your_token_here"Setting Up Webhook Listener
Start the Listener
Option 1: Connect to Your Local Server
paystack-cli webhook listen [url]If you don't provide a route, you'll be prompted:
$ paystack-cli webhook listen
Enter the url to listen on for webhooks: http://localhost:3000/webhookOption 2: Use Built-in Server
The CLI can start a simple server for you:
paystack-cli webhook listen --serveThis starts a server on port 3000 and automatically sets up the tunnel.
What Happens
When you start the listener:
- Ngrok tunnel is created to your local server (or built-in server)
- Webhook URL is updated in your Paystack integration
- Events are forwarded to your local endpoint automatically
- You'll see real-time webhook events in your terminal
Example Output
$ paystack-cli webhook listen http://localhost:3000/webhook
✓ Tunnelling webhook events to http://localhost:3000/webhook
✓ INFO: Listening for incoming webhook events at: http://localhost:3000/webhook
✓ INFO: Webhook URL set to: https://abc123.ngrok-free.app/webhook for test domain
✓ INFO: Press Ctrl+C to stop listening for webhook events.Testing Webhooks
Using webhook ping
While the listener is running, open another terminal and send test events:
# Test charge success
paystack-cli webhook ping --event=charge.success
# Test transfer success
paystack-cli webhook ping --event=transfer.success
# Test failed transfer
paystack-cli webhook ping --event=transfer.failed
# Test subscription creation
paystack-cli webhook ping --event=subscription.createInteractive Event Selection
If you don't specify an event, you'll be prompted to choose:
$ paystack-cli webhook ping
? Select event to simulate
❯ Charge Success
Transfer Success
Transfer Failed
Subscription Create
Customer Identification Failed
Customer Identification Success
DVA Assign Failed
DVA Assign SuccessModify Webhook Payload
You can interactively modify the webhook payload before sending:
paystack-cli webhook ping --event=charge.success --modThis allows you to:
- Add custom properties to nested objects
- Test different scenarios with custom data
Real Transaction Testing
You can also trigger webhooks by performing real actions:
# Initialize a transaction
paystack-cli transaction:initialize \
--email=test@example.com \
--amount=10000
# Complete the payment through the returned authorization URL
# The webhook will be sent to your local endpointAvailable Webhook Events
The CLI supports testing these webhook events:
| Event | Description |
|---|---|
charge.success | Successful payment transaction |
transfer.success | Successful money transfer |
transfer.failed | Failed transfer attempt |
subscription.create | New subscription created |
customeridentification.failed | Failed customer identification attempt |
customeridentification.success | Successful customer identification attempt |
dedicatedaccount.assign.failed | Failed DVA assignmenment attempt |
TIP
You can test any of these events without a running listener by using the --forward option with webhook ping to send to a specific URL.
Advanced Options
Specify Domain
Use test or live mode webhooks:
# Test mode (default)
paystack-cli webhook listen http://localhost:3000/webhook --domain=test
# Live mode
paystack-cli webhook listen http://localhost:3000/webhook --domain=liveForward to Specific URL
When using webhook ping, override the saved webhook URL:
paystack-cli webhook ping \
--event=charge.success \
--forward=https://your-custom-url.com/webhookThis sends the webhook to the specified URL instead of the configured webhook endpoint.
WARNING
The --forward option only works with webhook ping, not with webhook listen.
Custom Ngrok Domain
If you have a paid ngrok plan with a custom domain:
export NGROK_DOMAIN="your-subdomain.ngrok.io"
paystack-cli webhook listen http://localhost:3000/webhookCommand Reference
webhook listen
Start webhook listener and create ngrok tunnel.
paystack-cli webhook listen [url] [options]Arguments:
url- (Optional) Your local webhook endpoint (e.g.,http://localhost:3000/webhook)
Options:
--domain, -D- Domain to use (test/live) - Default:test--serve, -S- Start a built-in server on port 3000
Examples:
# With your own server
paystack-cli webhook listen http://localhost:3000/webhook
# With built-in server
paystack-cli webhook listen --serve
# On live domain
paystack-cli webhook listen http://localhost:3000/webhook --domain=live
# Interactive prompt if no URL provided
paystack-cli webhook listenwebhook ping
Send test webhook event to your configured endpoint.
paystack-cli webhook ping [options]Options:
--event, -E- Event type to simulate--domain, -D- Domain to ping (test/live) - Default:test--forward, -F- Send to specific URL instead of saved webhook--mod, -M- Interactively modify payload before sending
Examples:
# Send charge success event
paystack-cli webhook ping --event=charge.success
# Send to custom URL
paystack-cli webhook ping --event=transfer.success --forward=https://example.com/webhook
# Modify payload interactively
paystack-cli webhook ping --event=charge.success --mod
# Interactive event selection
paystack-cli webhook pingWebhook Handler Example
Here's an example Express.js webhook handler:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
// Verify webhook signature
const hash = crypto
.createHmac('sha512', process.env.PAYSTACK_SECRET_KEY)
.update(JSON.stringify(req.body))
.digest('hex');
if (hash === req.headers['x-paystack-signature']) {
// Process the event
const event = req.body;
console.log('Event:', event.event);
console.log('Data:', event.data);
// Handle different event types
switch (event.event) {
case 'charge.success':
// Handle successful charge
break;
case 'transfer.success':
// Handle successful transfer
break;
// ... other events
}
res.sendStatus(200);
} else {
res.sendStatus(400);
}
});
app.listen(3000, () => {
console.log('Webhook server listening on port 3000');
});Troubleshooting
"Ngrok token not configured"
Solution: Configure your ngrok auth token
paystack-cli config
# Select "Ngrok Auth Token" and enter your token"Port already in use"
Solution: Either:
- Stop the process using that port
- Use a different port in your local route
- Use
--serveflag which automatically uses port 3000 - Kill existing ngrok process:
pkill ngrok
Webhook not received
Checklist:
- Is the listener running? Check terminal output
- Is your local server running on the specified port?
- Is the route correct? (e.g.,
/webhooknot/webhooks) - Try using
--serveflag to use the built-in server - Check firewall/antivirus settings
"Session expired"
Solution: Your CLI session expired, login again:
paystack-cli loginNgrok tunnel not starting
Solution:
- Verify your ngrok auth token is correct
- Kill any existing ngrok processes:
pkill ngrok - Check your internet connection
- Try updating ngrok:
npm update -g @ngrok/ngrok
Best Practices
- Use Test Mode - Always use test mode for webhook testing
- Verify Signatures - Always verify webhook signatures in production
- Log Events - Log all webhook events for debugging
- Handle Duplicates - Webhooks may be sent multiple times, implement idempotency
- Return 200 Quickly - Acknowledge receipt immediately, process asynchronously
Next Steps
- Learn about Examples for webhook handling patterns
- Explore the API Reference for triggering events
- Read about Troubleshooting common issues