The Quiet Kind of Security Failure
Most small business software today is stitched together by webhooks — little HTTP calls that one service makes to another whenever something happens. A lead fills out a form, a payment processes, a voice agent finishes a call — each of these often fires off a webhook that tells your backend what to do next.
Every vendor offering webhooks also publishes a way to verify them: a signature header, usually computed with a shared secret. The idea is simple. Your backend checks the signature before trusting the data. No valid signature, no action.
The problem is that a very common implementation of signature verification is secretly broken. It looks correct. It passes code review. It handles the happy path perfectly. And under the wrong conditions, it will silently accept completely unauthenticated requests — for weeks or months, with no error, no alert, and no log entry that tells you something's wrong.
This pattern has a name. It's called fail-open, and it's one of the most insidious bugs in modern business software.
What "Fail-Open" Looks Like in Practice
Fail-open happens when a security check has a permissive fallback path — a branch of the code that silently lets a request through when some precondition isn't met.
In pseudocode, it looks something like this:
if secret_key is set AND signature header is present: verify(request)else: accept(request) # the trap
Read carefully. The verification only runs when both the secret key and the signature header are present. If either is missing, the request is just accepted. No rejection. No error. No log line.
This pattern usually enters a codebase for well-meaning reasons. A developer wants to allow local testing without needing a real secret. Or they want the code to "still work" in a staging environment where the secret hasn't been configured yet. Or they think of the missing key as an "optional" feature rather than a hard requirement.
All of those intentions are fine. The execution is not. Because the fallback runs in production too, the moment something happens to the secret — a misconfigured deployment, a secret that was recreated without the key, an env var that didn't make it to the new server — the whole authentication layer evaporates.
The rule: A security check with a permissive fallback path is indistinguishable from no security check at all. The only acceptable "else" in an authentication branch is "reject."
Why the Bug Lives for Months
What makes fail-open especially dangerous is that it produces no visible symptom in normal operation.
Your logs show requests being handled successfully. Your integration tests pass because they exercise the signed-request path. Your monitoring dashboards are green. Your real-world traffic still has most requests coming from the legitimate sender, which does include the signature, so everything looks fine from the outside.
Meanwhile, any attacker who stumbles across your webhook URL can send whatever they want and your system will process it as real.
In security audits, this kind of bug typically lives for 30 to 90 days before anyone notices — and usually the only reason it gets caught is someone running an intentional negative test. If nobody's ever tried sending an unauthenticated request on purpose, you don't know whether your system rejects it.
How to Test Any Webhook for Fail-Open in Under 60 Seconds
The test is simple enough that you can run it against any of your own webhook endpoints right now:
- Find the URL your vendor (Retell, Twilio, Stripe, Calendly, whatever) is posting to on your backend.
- Send a plain HTTP POST to that URL with any JSON body and no signature header.
- Look at the response.
A correctly-configured webhook should return 401 Unauthorized or 403 Forbidden. Anything else — a 200, a 500, a 204, even a polite "missing field" error — means the request got past the authentication layer, and that's a failure.
The Second Test: Forged Signature
Run the same test again, but this time include a signature header with a made-up value. The response should still be 401 or 403. Two distinct failure modes to cover:
- No signature at all — the request shouldn't even reach your business logic
- Invalid signature — the signature is present but doesn't compute correctly
Both must be rejected. If only one is, you still have a gap.
When It's Not Your Code
If your webhook is managed by a vendor plugin or no-code platform (Zapier, Make, Pipedream, n8n, etc.), run the same test against the public URL. Their verification layer is outside your control, but you can still confirm it rejects unauthenticated requests. If it doesn't, escalate to the vendor.
The Broader Lesson: Test the Negative Path
Fail-open is just one example of a broader pattern: a security control that only tests for success is not a security control.
Most test suites and QA processes exercise the happy path. The customer fills out the form correctly. The vendor sends a properly signed webhook. The auth token is valid. Everything works.
The attacker doesn't use the happy path. The attacker uses every other path. So your security testing has to do the same — intentionally sending malformed, unauthenticated, and malicious requests and confirming the system rejects them with the correct status code.
Practical questions to add to any pre-deploy review:
- What happens when the secret is missing from the configuration?
- What happens when the signature header is absent?
- What happens when the signature header is present but forged?
- What happens when a real request is replayed an hour later?
- What happens when the request body is modified after being signed?
Every one of those questions should have a test case and a known, documented rejection response.
What to Ask Your Vendors
If you're paying for a service that hands off work via webhooks, you can and should ask the vendor the following:
- "Do you sign your webhooks?" If the answer is no, their integration is not production-safe for anything sensitive.
- "What's the signature algorithm, and where is it documented?" HMAC-SHA256 is standard. If they can't point you to documentation, that's a warning sign.
- "What happens on your end if my secret is missing?" If they say "we'll log a warning and continue," walk away. You want an integration that fails closed on both ends.
- "Do you include a timestamp in the signed payload?" Without one, a valid signature can be replayed indefinitely. A properly signed webhook has a rolling freshness window, usually 5 minutes.
These questions aren't adversarial. A competent vendor will have direct answers to all of them. A vendor that can't answer is telling you something about how their product is built.
A Small Business Webhook Security Checklist
Before going live with any webhook integration — whether it's your own code, your vendor's, or a no-code platform:
Before Deploy
- The signature verification code has an explicit reject branch — no silent pass-through
- The signature check runs before any business logic, not after
- Secret values are stored in a secure secret store, not committed to the code repository
- There's a non-production way to test the integration without disabling verification
After Deploy
- Send an unsigned POST to the webhook URL — confirm 401 or 403
- Send a POST with a fake signature header — confirm 401 or 403
- Send a properly-signed request from the real vendor — confirm 200 and that the action happens
- Audit logs show an authentication decision on every request, not just successful ones
Monthly
- Re-run the unsigned-request test — confirm nothing regressed
- Rotate webhook secrets if supported by the vendor
- Review any new webhook endpoints added since the last audit
Conclusion
Fail-open isn't an exotic attack. It's a pattern that ships to production constantly, in codebases of every size, because it looks correct in code review and passes every test that checks only the happy path.
The fix is cultural, not technical. Every authentication branch in your code should explicitly reject requests that don't meet the precondition — never silently accept them. Every new integration should be tested against unauthenticated requests before it goes live. Every vendor should be asked how their verification handles misconfiguration.
If you've never sent an unsigned POST to your own webhook endpoints, that's the first 60-second job worth doing this week. You'll either confirm your authentication is working, or you'll find a backdoor you didn't know was open. Either answer is useful.