Skip to content

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:

  1. Creating an ngrok tunnel to your local server
  2. Automatically updating your Paystack webhook URL
  3. 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 ​

bash
paystack-cli config
# Select "Ngrok Auth Token"
# Paste your token

Or set it via environment variable:

bash
export NGROK_AUTH_TOKEN="your_token_here"

Setting Up Webhook Listener ​

Start the Listener ​

Option 1: Connect to Your Local Server ​

bash
paystack-cli webhook listen [url]

If you don't provide a route, you'll be prompted:

bash
$ paystack-cli webhook listen
Enter the url to listen on for webhooks: http://localhost:3000/webhook

Option 2: Use Built-in Server ​

The CLI can start a simple server for you:

bash
paystack-cli webhook listen --serve

This starts a server on port 3000 and automatically sets up the tunnel.

What Happens ​

When you start the listener:

  1. Ngrok tunnel is created to your local server (or built-in server)
  2. Webhook URL is updated in your Paystack integration
  3. Events are forwarded to your local endpoint automatically
  4. You'll see real-time webhook events in your terminal

Example Output ​

bash
$ 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:

bash
# 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.create

Interactive Event Selection ​

If you don't specify an event, you'll be prompted to choose:

bash
$ 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 Success

Modify Webhook Payload ​

You can interactively modify the webhook payload before sending:

bash
paystack-cli webhook ping --event=charge.success --mod

This 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:

bash
# 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 endpoint

Available Webhook Events ​

The CLI supports testing these webhook events:

EventDescription
charge.successSuccessful payment transaction
transfer.successSuccessful money transfer
transfer.failedFailed transfer attempt
subscription.createNew subscription created
customeridentification.failedFailed customer identification attempt
customeridentification.successSuccessful customer identification attempt
dedicatedaccount.assign.failedFailed 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:

bash
# Test mode (default)
paystack-cli webhook listen http://localhost:3000/webhook --domain=test

# Live mode
paystack-cli webhook listen http://localhost:3000/webhook --domain=live

Forward to Specific URL ​

When using webhook ping, override the saved webhook URL:

bash
paystack-cli webhook ping \
  --event=charge.success \
  --forward=https://your-custom-url.com/webhook

This 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:

bash
export NGROK_DOMAIN="your-subdomain.ngrok.io"
paystack-cli webhook listen http://localhost:3000/webhook

Command Reference ​

webhook listen ​

Start webhook listener and create ngrok tunnel.

bash
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:

bash
# 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 listen

webhook ping ​

Send test webhook event to your configured endpoint.

bash
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:

bash
# 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 ping

Webhook Handler Example ​

Here's an example Express.js webhook handler:

javascript
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

bash
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 --serve flag which automatically uses port 3000
  • Kill existing ngrok process: pkill ngrok

Webhook not received ​

Checklist:

  1. Is the listener running? Check terminal output
  2. Is your local server running on the specified port?
  3. Is the route correct? (e.g., /webhook not /webhooks)
  4. Try using --serve flag to use the built-in server
  5. Check firewall/antivirus settings

"Session expired" ​

Solution: Your CLI session expired, login again:

bash
paystack-cli login

Ngrok tunnel not starting ​

Solution:

  1. Verify your ngrok auth token is correct
  2. Kill any existing ngrok processes: pkill ngrok
  3. Check your internet connection
  4. Try updating ngrok: npm update -g @ngrok/ngrok

Best Practices ​

  1. Use Test Mode - Always use test mode for webhook testing
  2. Verify Signatures - Always verify webhook signatures in production
  3. Log Events - Log all webhook events for debugging
  4. Handle Duplicates - Webhooks may be sent multiple times, implement idempotency
  5. Return 200 Quickly - Acknowledge receipt immediately, process asynchronously

Next Steps ​

Released under the ISC License.