[KongchangAI]
· 2 min read· 1,294 words

A 24-Hour Webhook Catcher Ate a Bug That Only Fired on the 25th Hour

A 24-Hour Webhook Catcher Ate a Bug That Only Fired on the 25th Hour

A webhook catcher's 24-hour silent TTL masked a payment provider's 25th-hour retry bug—the lesson is to make expiration explicit and visible.

This article recounts a debugging predicament caused by a temporary webhook catcher's 24-hour TTL silently expiring. The author hooked it up to a payment provider's callback; the first delivery went fine, but the provider had a retry-logic bug—when the ACK was slow, it would wait exactly 24 hours before retrying. By the 25th hour, the catcher had already auto-deleted, the request returned a 404, yet the provider's dashboard still showed 'delivered,' leading the author to mistakenly believe their tool was dropping events. The root cause wasn't dropped events, but that expiration is invisible to the caller. The fix: introduce a ?ttl= parameter with a hard cap and explicitly return expires_at in every response, turning an implicit lifecycle into an explicit contract. The article draws a general principle: all ephemeral infrastructure should make expiration a loud signal, not a silent disappearance.

A temporary webhook receiver designed to let AI agents test callbacks from services like Stripe and GitHub without spinning up their own server. The design is simple: a single-use HTTPS URL, accepts any POST, stores up to 100 events, and auto-deletes after 24 hours.

Sounds like a clean, no-nonsense tool. But the author recently shared a mishap: that 24-hour TTL quietly turned an integration test into "false confidence."

RSS source: My webhook catcher's 24h TTL ate a bug that only fired on the 25th hour

What Went Wrong

The author hooked this temporary webhook catcher up to a payment provider's callback. The first webhook fired normally, the ACK was returned—but the payment provider had a retry-logic bug: if the ACK was slow, it would wait exactly 24 hours before retrying.

By the time the retry went out on the 25th hour, the catcher had already auto-deleted. The retry hit a URL that no longer existed and returned a 404. Yet the payment provider's dashboard still showed "delivered."

The result: the author spent two full days troubleshooting, suspecting the catcher was dropping events. In reality, the catcher was working perfectly—it had just disappeared on schedule, and that "disappearing on schedule" happened to mask the other side's retry-logic bug.

Webhook and ACK mechanics: A webhook is a pattern where a server pushes events to a URL that the caller has pre-registered, as opposed to polling. After processing the event, the receiver typically needs to return a 2xx response—that response is the ACK (acknowledgement). If the sender doesn't receive an ACK within the timeout window, it treats the delivery as failed and retries according to a defined backoff strategy. Because of this, ACK latency and retry strategy are tightly coupled—many providers use exponential backoff, but some implementations degrade to fixed intervals or even extremely long waits under certain conditions. In this case, the payment provider waited a full 24 hours to retry when the ACK was slow, and that duration happened to equal the catcher's TTL, causing the retry to hit a deleted URL. Understanding ACK and retry semantics is key to determining "who's responsible for the dropped event."

Why Silent Expiration Is Dangerous

This problem is worth pondering for anyone building developer tools.

The core value of ephemeral tools is "use it and walk away"—no resource footprint, no leftover garbage, automatic cleanup. But the side effect of automatic cleanup is that expiration is silent. Users can't see a countdown, callers don't know when a resource will vanish, and once the behavior of the thing under test crosses the TTL boundary, the failure gets misattributed.

The author put it precisely: "If the test scaffolding can vanish on a timer, the thing you're testing can fail in a way that looks like your tool is broken."

This is the worst class of debugging failure—the error message isn't where the real problem is.

TTL (Time To Live): TTL is originally a common concept in networking and caching, referring to the maximum amount of time a piece of data or a resource is allowed to exist before being automatically invalidated. In cloud-native and ephemeral infrastructure contexts, TTL is usually enforced by a background reaper that periodically scans and deletes expired objects, rather than being triggered by the user. The defining trait of this lazy reclamation is that the deletion happens server-side, and the caller knows nothing about it. The contrast is explicit release (such as calling a revoke endpoint), where the caller gets immediate feedback. The hidden danger of silent TTL is that it makes "resource doesn't exist" and "resource never existed" look like the same error (usually a 404), and the caller can't distinguish between the two—which means they can't tell whether they misconfigured something or the other side's behavior crossed the TTL boundary.

The Fix: Make Expiration "Loud"

In the end, the author's fix wasn't to add storage or extend the TTL. Both of those paths just push the problem down the road.

The approaches that actually worked were two:

  1. Introduce a ?ttl= parameter with a hard upper limit. Callers can specify their own lifetime, but the cap is enforced server-side to prevent indefinite occupation.
  2. Explicitly return expires_at in every response. Every time the caller gets data, they know when this "rug" will be pulled out from under them.

The value of these two changes is turning an implicit lifecycle into an explicit contract. After the fix, that payment provider's retry bug was immediately exposed in the next test round—because the retry was actually captured, instead of hitting a URL that had already vanished.

Lessons for the Agent Toolchain

Although this case comes from a tiny webhook catcher, it points to a more general engineering principle.

More and more AI agents now rely on ephemeral infrastructure for integration testing: temporary databases, temporary sandboxes, temporary API mocks, temporary tunnels. What these tools share is a short lifecycle, automatic reclamation, and no cleanup required. Convenient, sure—but they all carry the same hidden hazard: expiration is invisible to the caller.

If you're designing this kind of tool, here's a checklist:

  • Does every response include the resource's remaining lifetime or absolute expiration time?
  • Is the TTL hardcoded or configurable? Is there an upper-bound safeguard?
  • When a resource disappears, does the caller get an explicit "expired" signal, or a vague 404?
  • Does the documentation clearly state "how long until this resource vanishes"?

The author ultimately packaged the tool and put it on x402.freeq.one, with a very straightforward usage model: fire any POST at it, and read the events back before the clock runs out. The tool itself didn't get more complex—what changed is that it's more honest with callers.

A 24-hour TTL isn't a problem in itself. The problem is that it was too quiet.

Ephemeral infrastructure and explicit contracts: Temporary databases, temporary sandboxes, temporary API mocks, temporary tunnels—what they share is on-demand creation, short lifecycles, and automatic reclamation. They dramatically reduce the orchestration cost of integration testing, but they also turn lifecycle management from an "explicit action" into an "implicit convention." The more robust engineering approach is to elevate the lifecycle into a contract: return an absolute expiration time (expires_at) when the resource is created, and repeat the remaining lifetime in every interaction response; the caller can then proactively judge whether the resource is still valid, or even renew it ahead of time. This pattern is already mature in scenarios like HMAC signatures, OAuth tokens, and S3 presigned URLs—all of them without exception write the expiration time into the credential itself, rather than relying on a mutual understanding of duration.

One-line Summary

A webhook catcher's 24-hour silent TTL masked a payment provider's 25th-hour retry bug—the lesson is to make expiration explicit and visible.

Paragraph Summary

This article recounts a debugging predicament caused by a temporary webhook catcher's 24-hour TTL silently expiring. The author hooked it up to a payment provider's callback; the first delivery went fine, but the provider had a retry-logic bug—when the ACK was slow, it would wait exactly 24 hours before retrying. By the 25th hour when the retry went out, the catcher had already auto-deleted, the request returned a 404, and the provider's dashboard still showed "delivered," leading the author to mistakenly believe their tool was dropping events. The root cause wasn't dropped events, but that expiration is invisible to the caller, causing the failure to be misattributed. The fix is to introduce a ?ttl= parameter with a hard upper limit, and explicitly return expires_at in every response, turning an implicit lifecycle into an explicit contract. After the fix, the provider's retry bug was immediately exposed. The article draws out a general principle: all ephemeral infrastructure should make expiration a loud signal, not a silent disappearance.

#webhook catcher#TTL expiration#ephemeral infrastructure#retry bug#AI agent testing#expires_at contract#developer tools#integration testing
Share:

Related articles