Twilio webhook refused every live call
401 WWW-Authenticate Basic Twilio webhook1What happened
when someone phones one of our numbers, Twilio sends a request to a web address on our server. That address is called a webhook.
Our voice webhook took any request that named one of our numbers. It never checked that the request came from Twilio. Our security review found it on Oct 18 and rated it Medium.
The risk: anyone who learned the address could fake a call. Each fake call would start a session with our AI voice agent. That costs AI minutes, and enough fakes could run up the bill or tie up the line.
The fix: a username and password inside the Twilio voice URL (HTTP Basic auth).
The first version answered "401, not allowed" when the login was missing. Our tests passed. But Twilio never sends the login on its first try. It sends it only after a 401 that also carries a WWW-Authenticate: Basic header.
A plain 401 would have turned away every live call.
2What we did
A request with no login now gets a 401, a WWW-Authenticate: Basic header, and a reply that rejects the call. All of this happens before any lookup, any AI session start and any database write.
For buildersthe header isWWW-Authenticate: Basic realm="voice"and the reply body is<Response><Reject/></Response>.The login is compared in constant time, so its length and letters can't be guessed from how long the check takes.
For buildershash both sides with sha256, then compare with crypto.timingSafeEqual.The per-IP rate limit now runs after the login check. Twilio's first try, the one that gets the 401, had used up the limit, so live calls got blocked with a 429 after a few.
The login uses only letters and numbers. Twilio doesn't support special characters in it, and our first login had a hyphen.
The login also goes in the URL of the "press 9 to stop" step. Without it, every opt-out on a locked number got a 401.
The login lives in one production-only, sensitive setting on the host. If the setting is missing, calls are refused and an error is logged. Nothing falls open.
A second setting lets the login change with no refused calls: both logins work during the switch.
3How we know it worked
on the live site, checked by people other than the builder.
A request with no login got a 401 with the Basic challenge. So did a wrong login.
A live call reached the agent. The log showed two requests 0.24 seconds apart: the 401 challenge, then Twilio's retry with the login.
Live calls to our other numbers played the "press 9 to stop" line, then reached the agent.
179 voice tests pass, including tests that fail on the old code.
4What you can take from it
a 401 without WWW-Authenticate: Basic passes every test and still breaks every live call. Test with a live phone call, not only your own requests.
