I built a throwaway webhook receiver because I was tired of spinning up tunnel daemons just to inspect one raw payload. It hands you a temporary HTTPS URL, records every POST that hits it, and lets you read the events back for 24 hours — I ended up packaging it as freeq.one/tools/webhook_catch.html.
Last week it nearly cost me an afternoon. A repo's push events were feeding an internal handler, and the handler kept silently ignoring its deliveries. So I pointed a catch URL at the same events to see exactly what the sender was emitting. A big force-push landed — a few hundred commits crammed into one update — and the events burst in over roughly a minute.
When I read the catch URL back, the first stored event was already in the middle of the burst. The hook's initial ping, which contains the configuration echo my handler depended on, wasn't there at all. I spent about an hour assuming the sender never delivered it: re-read the sender's webhook docs, checked my DNS, recreated the hook. Nothing.
The actual answer was mine. The catcher stores events in memory with a hard cap of 100, and a burst of 140 events had evicted the oldest ones — ring-buffer style, with no warning. The buffer didn't fail loudly; it just quietly shifted its floor and made the missing event indistinguishable from "the sender never sent it."
The fix took one evening, and the changes matter more than the bug:
- The read response now includes an
events_droppedcounter, so eviction is visible data instead of a silent hole. - The very first event is pinned — pings carry the hook metadata that everything after it assumes.
- The creation response now returns an explicit
expires_at, because "24 hours" means nothing to a script reading the response three weeks later.
Lesson I keep relearning: a bounded store with no overflow signal is indistinguishable from data loss at the source. If your system ever throws anything away, the API saying so is part of the API.
Top comments (0)