How to debug a webhook that never arrives.

•

•

The Webhook They Swear They Sent

I remember sitting in my kitchen at 2:15 AM, the only light coming from a dual-monitor setup and the blue glow of a half-empty energy drink, staring at a dashboard that insisted everything was fine. On paper, the third-party provider was sending the data; in reality, my database was bone dry. There is no specific kind of dread quite like the silence of a missing event, and most tutorials online won’t help you when you’re trying to figure out how to debug a webhook that never arrives. They’ll tell you to check your endpoint URL or your authentication headers, but they rarely talk about the silent killers—the subtle network timeouts, the aggressive WAF rules, or the way a load balancer decides to drop a packet without saying a word.

I’m not here to sell you on a new observability suite or a fancy distributed tracing tool that costs more than my first car. Instead, I want to walk through the actual, messy checklist I use when I’m the one on call and things are breaking. We’re going to look at the uncomfortable trade-offs of logging, how to verify the handshake actually happened, and how to build enough visibility so you aren’t just guessing in the dark next time.

Table of Contents

Hunting for Clues in Your Webhook Listener Server Logs

Hunting for Clues in Your Webhook Listener Server Logs

The first thing I do—and the thing I should have done more often in my early career—is stop guessing and start looking at the webhook listener server logs. If the request isn’t hitting your application layer, you’re chasing ghosts. I once spent an entire afternoon debugging a service integration only to realize the traffic wasn’t even reaching my code; it was being dropped at the load balancer because of a mismatch in expected headers. You need to verify if the TCP connection is even being established. If your logs are silent, you aren’t looking at an application bug; you’re likely dealing with a network-level issue, such as a firewall blocking incoming requests from the provider’s IP range.

Once you confirm the traffic is actually arriving, the focus shifts to the handshake. I’ve learned to treat every incoming request as a potential crime scene. I start with a basic webhook payload inspection to see if the data structure matches what the documentation promised. Often, the provider changes a field name or wraps the object in a new metadata layer without much fanfare. If the logs show the request arrived but your application responded with a 4xx or 5xx, you’ve moved into the realm of HTTP status code troubleshooting. At that point, the mystery isn’t “where is the data?” but rather “why did my parser choke on it?”

Why Your Firewall Might Be Blocking Incoming Requests

Why Your Firewall Might Be Blocking Incoming Requests

I once spent an entire afternoon debugging a service that seemed to have simply died, only to find out the infrastructure team had tightened the security group rules without telling anyone. It’s a classic scenario: your code is perfect, your listener is running, but the packets are being dropped at the perimeter. If you aren’t seeing anything in your webhook listener server logs, the problem is almost certainly upstream. You need to stop looking at your application code and start looking at your network ingress.

The first thing I do is run some basic endpoint connectivity testing from an external environment—something outside your VPC or local network. If you can’t reach your endpoint with a simple `curl` command from a different network, you aren’t dealing with a code bug; you’re dealing with a gatekeeper. Check if your firewall is actually allowing traffic on the specific port you’ve exposed. It sounds trivial, but I’ve seen countless hours wasted on complex logic when the reality was just a missing rule for firewall blocking incoming requests. If the handshake never happens, your application never even knows it was supposed to be working.

Five things I learned from watching payloads disappear

  • Check the sender’s outgoing logs, not just your own. I once spent an entire afternoon debugging my listener only to find out the third-party service had a “retry on failure” logic that was actually working perfectly—it just wasn’t sending the first attempt because of a configuration error on their end.
  • Don’t trust a 200 OK if you aren’t actually reading the body. I’ve seen plenty of “successful” deliveries where the server responded with a 200 because the routing was fine, but the application layer silently dropped the payload due to a schema mismatch. Log the raw bytes if you have to.
  • Watch out for the “silent timeout” trap. If your processing logic takes longer than the sender’s timeout threshold, they’ll mark it as a failure and stop sending. If you’re doing heavy lifting like database writes or third-party API calls inside the webhook handler, move that to a background queue immediately.
  • Verify the signature before you get too deep. If you’re using HMAC or some other security layer, a single character mismatch in your secret key will cause your middleware to reject the request before it even hits your application logs. I’ve wasted hours on this because of a trailing newline in an environment variable.
  • Test with a tool that mimics the real world, like ngrok or a dedicated webhook tester. Trying to debug by manually sending `curl` commands often misses the nuances of how the actual provider structures headers or handles connection persistence, which is usually where the real friction lies.

The Post-Mortem Checklist

Don’t assume the sender is at fault; I’ve lost more hours blaming a third-party API when the reality was just a silent 500 error on my own ingress controller.

If you aren’t logging the raw request body immediately upon arrival, you aren’t debugging—you’re just guessing.

Always verify your handshake; half the time the “missing” webhook is actually just a failed TLS negotiation that your load balancer decided to drop without a word.

The Post-Mortem Mindset

Debugging with The Post-Mortem Mindset.

At the end of the day, debugging a missing webhook is rarely about a single catastrophic failure and usually about a series of small, invisible gaps. You have to check the obvious stuff first—the listener logs that show nothing, the firewall rules that are too tight, and the load balancer configurations that might be dropping packets before they even reach your application code. I’ve learned the hard way that if you don’t have visibility at every hop of the request’s journey, you aren’t actually debugging; you’re just guessing. It’s about narrowing the search area from the entire internet down to a specific line in a specific log file.

If you find yourself staring at a blank dashboard at midnight, don’t beat yourself up. We’ve all been there, and most of these “mysterious” failures are just the universe reminding us that distributed systems are inherently messy. The goal isn’t to build a perfect system that never fails, because that system doesn’t exist. The goal is to build a system that is easy to observe when it inevitably does. Write down what you found, update your documentation, and move on. The best engineers aren’t the ones who never break things; they’re the ones who know exactly how to find the pieces when they do.

Frequently Asked Questions

What if the logs show a 200 OK, but my database still hasn't updated?

This is the classic “phantom success” scenario. If the logs say 200 OK, your web server thinks its job is done, but the data didn’t land. I’ve seen this happen because of uncommitted database transactions or a silent failure in an async worker. You likely accepted the payload, sent the success response immediately, and then the background process died halfway through. Check your worker queues and wrap your DB logic in explicit error handling.

How can I test my webhook endpoint locally without exposing my entire machine to the public internet?

I used to try and open ports on my router like it was 2005, which is a great way to get your machine scanned by every bot on the planet. Now, I just use a tunnel. Tools like ngrok or Cloudflare Tunnel are the standard for a reason: they create a secure bridge to your localhost. If you want something more lightweight and open-source, try localtunnel. It lets you test the full lifecycle without exposing your entire home network.

At what point do I stop blaming my own infrastructure and start assuming the sender is the one failing?

I usually draw the line when I can prove, via a simple `curl` or a packet capture, that the request never even touched my network interface. If my edge logs are silent and my firewall is wide open, the problem isn’t my infra; it’s their egress. I stop blaming myself once I have a timestamped trace showing a successful handoff from my side that simply never materialized on the other. At that point, it’s their turn to check their logs.

Is there a way to implement a retry mechanism that won't accidentally DDOS my own service when things get shaky?

I’ve seen this happen: a service slows down, the webhooks start failing, and then your own retry logic acts like a self-inflicted DDoS attack. To avoid this, you need two things: exponential backoff and jitter. Don’t just retry every 30 seconds; increase the delay progressively. More importantly, add some randomness—jitter—to those delays. If a thousand webhooks fail at once, you don’t want them all hammering your server again at the exact same millisecond.

Tomasz Wierzbicki

About Tomasz Wierzbicki

I am not interested in arguing about frameworks. I am interested in why the thing broke at two in the morning and what would have prevented it. Most of what I know came from being wrong in production and writing it down afterwards.