Bonaventure OgetoBy Bonaventure Ogeto|

Paystack Webhook Not Firing: Diagnostic Checklist

Check these in order: (1) Your webhook URL is correctly entered in the Paystack dashboard. (2) The URL uses HTTPS, not HTTP. (3) Your domain DNS resolves correctly. (4) Your firewall allows inbound HTTPS traffic. (5) Your server is running and the route exists. (6) Your endpoint returns 200 within 10 seconds. (7) You are using the right mode (test vs live). (8) Paystack has not exhausted retries for your endpoint. Start at step 1 and work down. Most issues are caught in the first three steps.

Step 1: Check the Webhook URL in Dashboard

Log in to your Paystack dashboard. Go to Settings, then API Keys & Webhooks (or just Webhooks, depending on your dashboard version). Look at the webhook URL field.

Verify these things:

  • The URL is actually filled in. A blank URL means Paystack has nowhere to send events.
  • The URL points to your current server. If you redeployed to a new domain or changed your hosting, the old URL is dead.
  • The path is correct. https://yourapp.com/webhooks/paystack is different from https://yourapp.com/webhook/paystack (missing 's').
  • There are no trailing spaces or invisible characters. Copy the URL from the dashboard, paste it into your browser, and check it looks right.
  • The URL does not contain localhost. http://localhost:3000/webhooks/paystack is only reachable from your machine.

How to verify:

# From your local machine or any machine with internet access
curl -X POST https://yourapp.com/webhooks/paystack   -H "Content-Type: application/json"   -d '{"test": true}'

# You should get a response (even if it's a 401 or 400).
# If you get "Could not resolve host" or "Connection refused",
# the URL is wrong or your server is down.

Step 2: Confirm HTTPS

Paystack only sends webhooks to HTTPS URLs. If your URL starts with http://, Paystack will not send anything.

Check that:

  • Your URL starts with https://
  • Your SSL certificate is valid and not expired
  • Your certificate covers the exact domain (including subdomains)

How to verify:

# Check SSL certificate
openssl s_client -connect yourapp.com:443 -servername yourapp.com /dev/null | openssl x509 -noout -dates

# You should see:
# notBefore=...
# notAfter=...  (this date must be in the future)

If you are developing locally, you need a tunnel service to get a public HTTPS URL:

# Using ngrok
ngrok http 3000
# Gives you https://abc123.ngrok-free.app

# Using Cloudflare Tunnel
cloudflared tunnel --url http://localhost:3000
# Gives you https://something.trycloudflare.com

Set the tunnel URL as your webhook URL in the Paystack dashboard during development.

Step 3: Check DNS Resolution

Your domain must resolve to a valid IP address that Paystack's servers can reach.

How to verify:

# Check DNS resolution
dig yourapp.com +short
# Should return an IP address like 104.21.55.123

# Or use nslookup
nslookup yourapp.com
# Should show an Address line with your server's IP

Common DNS problems:

  • Domain not propagated. If you just set up the domain, DNS records can take up to 48 hours to propagate worldwide. Paystack's servers may not see the record yet.
  • Wrong DNS record. Your A record points to an old server IP. Update it to your current server.
  • Domain expired. Check that your domain registration has not lapsed.
  • Subdomain not configured. You set up yourapp.com but your webhook URL uses api.yourapp.com. The subdomain needs its own DNS record.

Step 4: Check Firewall and Security Groups

Your server must accept inbound HTTPS traffic (port 443) from Paystack's IP addresses.

How to verify:

# Test connectivity from outside your network
# Use a different machine or a service like https://reqbin.com
# Send a POST request to your webhook URL

# On AWS, check your Security Group inbound rules:
# Port 443 should be open to 0.0.0.0/0 (or at minimum, Paystack's IPs)

# On DigitalOcean, check your Firewall settings
# On Google Cloud, check your VPC firewall rules

Things that can silently block webhooks:

  • Cloud provider firewalls (AWS Security Groups, GCP Firewall Rules, Azure NSGs)
  • Host-level firewalls (ufw, iptables, firewalld on your server)
  • Cloudflare or CDN settings that challenge or block POST requests from unknown sources
  • Rate limiting rules on your load balancer or WAF that block Paystack's requests
  • IP allowlisting that does not include Paystack's outbound IP ranges

If you use Cloudflare in front of your server, make sure the webhook path is not behind a challenge page or bot protection rule. Paystack's servers will not solve CAPTCHAs.

Step 5: Check Your Server Is Running

This sounds obvious, but verify your application process is running and listening on the correct port.

How to verify:

# Check if your process is running
ps aux | grep node  # for Node.js
ps aux | grep python  # for Django
ps aux | grep php  # for Laravel

# Check if something is listening on the expected port
ss -tlnp | grep :3000  # replace 3000 with your port

# Check your app logs for crash errors
tail -100 /var/log/yourapp/error.log

Common issues:

  • The server crashed and was not automatically restarted. Use a process manager like PM2 (Node.js), systemd, or supervisord.
  • The server is running but on a different port than your reverse proxy expects.
  • The server started but the webhook route was not registered due to a code error.
  • On serverless platforms (Vercel, Netlify), the function may have been deleted or renamed.

Step 6: Check the Route Exists and Accepts POST

Your webhook endpoint must handle POST requests. A GET-only route will return 405 Method Not Allowed, and Paystack will treat that as a failure.

How to verify:

# Send a test POST request to your endpoint
curl -X POST https://yourapp.com/webhooks/paystack   -H "Content-Type: application/json"   -d '{"event":"charge.success","data":{}}'   -v

# Look at the response:
# 200 = your endpoint is reachable and responding correctly
# 404 = the route does not exist (wrong path)
# 405 = the route exists but does not accept POST
# 500 = your handler is crashing
# 301/302 = redirect (Paystack may not follow redirects)

Watch out for trailing slash issues. Some frameworks treat /webhooks/paystack and /webhooks/paystack/ as different routes. If your framework redirects from one to the other with a 301, Paystack may not follow the redirect. Set your webhook URL to match the exact path your server expects.

Step 7: Check Your Response Code and Timing

Paystack expects your endpoint to return HTTP 200 within approximately 10 seconds. If your endpoint returns anything other than 200, or if it takes too long, Paystack marks the delivery as failed and schedules a retry.

Common mistakes:

  • Returning 201 or 204. Paystack specifically looks for 200. A 201 Created or 204 No Content will be treated as a failure.
  • Doing heavy processing before responding. If you send emails, update databases, call external APIs, and do all of that before sending the 200, you may time out. Return 200 immediately, then process the event asynchronously.
  • Throwing an unhandled exception. An error in your handler code returns a 500. Paystack retries, hits the same error, and eventually gives up.

The correct pattern:

app.post('/webhooks/paystack', async (req, res) => {
  // 1. Verify signature (fast operation)
  // 2. Return 200 IMMEDIATELY
  res.sendStatus(200);

  // 3. Process the event after responding
  // Use a queue for reliable processing
  try {
    await processWebhookEvent(req.body);
  } catch (err) {
    console.error('Webhook processing failed:', err);
    // The 200 was already sent, so Paystack will not retry.
    // You need your own retry mechanism here.
  }
});

Better yet, push the event to a message queue (BullMQ, Celery, Laravel Queues) and let a worker process it. That way your webhook handler always responds in milliseconds.

Step 8: Test Mode vs Live Mode

Paystack test mode and live mode have separate webhook configurations. This catches many developers after their first deployment.

Symptoms:

  • Webhooks worked perfectly in test mode during development
  • You deploy to production and switch to live keys
  • Webhooks stop arriving

The cause: you set the webhook URL in test mode but never set it in live mode. Or you set a different URL in live mode that is wrong.

How to verify:

  1. Log in to the Paystack dashboard
  2. Toggle between Test Mode and Live Mode (top of the dashboard)
  3. Check the webhook URL in each mode
  4. Make sure both point to the correct production URL when you go live

Also verify that you are using the matching API key. If you initialize transactions with your live secret key, the webhooks will come from live mode. Your server must be set up to receive live webhooks at the URL configured in live mode.

Step 9: Check Paystack Retry Status

Paystack retries failed webhook deliveries with exponential backoff. After repeated failures, it may stop retrying for your endpoint.

How to check:

  1. Go to the Paystack dashboard
  2. Navigate to the Webhooks or Events section
  3. Look at the delivery history for recent events
  4. Check if deliveries show "Failed" status and what HTTP status code your server returned

The dashboard shows you the exact response your server returned for each webhook attempt. If you see a pattern of 500s or timeouts, that tells you exactly what to fix.

If Paystack has stopped retrying because of too many failures, you can manually retry from the dashboard once you have fixed the underlying issue. You can also trigger a new event (like a test transaction) to confirm your endpoint is now working.

For a deeper look at retry behavior, see Paystack Webhook Retries: What to Expect and How to Cope.

Step 10: End-to-End Verification

After fixing the issue, run this end-to-end test to confirm everything works:

  1. Add logging to your webhook handler. Log the timestamp, event type, and a confirmation that processing completed.
  2. Trigger a test event. Go to the Paystack dashboard, navigate to the test environment, and initiate a test payment. Some dashboard versions have a "Send Test Webhook" button.
  3. Check your server logs. You should see the incoming webhook request and your processing log.
  4. Verify in the dashboard. The webhook delivery status in the Paystack dashboard should show 200.
// Add this temporary logging to your webhook handler
app.post('/webhooks/paystack', (req, res) => {
  console.log('[WEBHOOK] Received at:', new Date().toISOString());
  console.log('[WEBHOOK] Headers:', JSON.stringify({
    'content-type': req.headers['content-type'],
    'x-paystack-signature': req.headers['x-paystack-signature'] ? 'present' : 'missing',
  }));
  console.log('[WEBHOOK] Body length:', JSON.stringify(req.body).length);

  // Your signature verification here...

  res.sendStatus(200);
  console.log('[WEBHOOK] 200 sent');
});

Once you confirm the test webhook arrives and is processed correctly, remove the verbose logging and keep only essential operational logs.

For the complete error reference, see Paystack Errors and Troubleshooting: The Complete Reference. If webhooks arrive but signature verification fails, see Paystack Signature Verification Failed: The Definitive Fix.

Key Takeaways

  • The most common reason webhooks do not arrive is a wrong or outdated URL in the Paystack dashboard. Check the URL character by character, including trailing slashes and path casing.
  • Paystack requires HTTPS for webhook URLs. HTTP endpoints will not receive webhooks. If you are developing locally, use ngrok or Cloudflare Tunnel to get an HTTPS URL.
  • Your server must return HTTP 200 within about 10 seconds. Paystack treats anything other than 200 as a failure and will retry. If your handler consistently times out, Paystack will eventually stop retrying.
  • Test mode and live mode have separate webhook configurations. A webhook URL set in test mode does not apply to live mode. If you just switched to live and webhooks stopped, this is likely the cause.
  • Paystack retries failed webhooks with exponential backoff. After enough failures, it may disable your endpoint. Check the Paystack dashboard for webhook delivery logs to see if requests were sent and what response they received.
  • Firewalls, security groups, and hosting platform restrictions can silently block Paystack webhook requests. Test that your endpoint is reachable from the public internet before blaming Paystack.

Frequently Asked Questions

Does Paystack send webhooks to HTTP (non-HTTPS) URLs?
No. Paystack requires HTTPS for webhook URLs. If your URL starts with http://, you will not receive any webhooks. For local development, use a tunnel service like ngrok or Cloudflare Tunnel to get a public HTTPS URL.
How many times does Paystack retry a failed webhook?
Paystack retries with exponential backoff over several hours. The exact number of retries and intervals are not publicly documented in precise detail, but you can expect several retry attempts spread over hours. After enough consecutive failures, Paystack may stop retrying for your endpoint entirely.
Can I use a subdomain for my webhook URL?
Yes. You can use any valid HTTPS URL, including subdomains like https://api.yourapp.com/webhooks/paystack. Make sure the subdomain has proper DNS records and a valid SSL certificate that covers it.
My webhook was working yesterday and stopped today. What happened?
The most common causes for sudden webhook failure are: your SSL certificate expired, your server crashed or was redeployed to a new URL, a firewall rule was changed, or Paystack stopped retrying after too many consecutive failures. Check the Paystack dashboard webhook logs to see what response code your server returned.
Can I have different webhook URLs for different event types?
No. Paystack sends all webhook events to a single URL per environment (test and live). You cannot route different event types to different URLs at the Paystack level. Your single webhook handler must inspect the event type and dispatch accordingly.

Ready to build real-world apps?

Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.

Apply to the McTaba Marathon