Describe the bug
I cannot connect a Twilio stream via websocket over cloudflared to my wrangler dev backend locally. This specific setup fails, but any other setup works fine!? I'm 50-60 hours into this bug, over 4 days, and my hair is all gray now :)
TL;DR: 4 setups:
- BROKEN:
cloudeflared locally + wrangler dev server + Twilio websocket = Twilio error "31920: Stream - WebSocket - Handshake Error ... server didn't respond with 101"
- WORKS:
cloudeflared + wrangler dev + manually connect to ws from websocketking.com (no twilio)
- WORKS:
ngrok + same wrangler dev + same Twilio websocket
- WORKS:
cloudeflared + node server.js (rewritten, non-CF Worker server) + Twilio websocket
To Reproduce
With a simple Hono router setup in a CF Worker, the following tries to construct a minimal websocket, but never receives any messages over it because the handshake fails. Twilio calls POST /incoming-call and receives a message to connect to the websocket on /media-stream. It tries, my server says it sent a 101 response, but Twilio says it never gets it (but the other setups do indeed work):
router.post('/incoming-call', validateTwilioRequest(), (c) => {
const host = c.req.header('host');
if (!host) {
console.error('Cannot determine host for WebSocket URL in /incoming-call');
return c.text('Server configuration error: Cannot determine host', 500);
}
const webSocketUrl = `wss://${host}/twilio-ai/media-stream`;
const twimlResponse = `<?xml version="1.0" encoding="UTF-8" ?>
<Response>
<Say>Starting websocket</Say>
<Connect>
<Stream url="${webSocketUrl}" />
</Connect>
<Say>This TwiML instruction is unreachable unless the Stream is ended by your WebSocket server.</Say>
</Response>`;
return c.text(twimlResponse, 200, { 'Content-Type': 'text/xml' });
});
router.get(
"/media-stream",
async (c) => {
const webSocketPair = new WebSocketPair();
const [client, server] = Object.values(webSocketPair);
try {
const upgradeHeader = c.req.header("Upgrade");
if (!upgradeHeader || upgradeHeader !== "websocket") {
return c.text("Expected Upgrade: websocket", 426);
}
// Handle incoming messages from Twilio
server.addEventListener("message", async (event: MessageEvent) => {
console.log('Message Event: ', event); // No messages are ever received bc handshake failed
}
);
server.addEventListener("close", async () => {
console.log('Close');
});
} catch (e) {
console.error("WebSocket setup error:", e);
return c.text("Internal Server Error", 500);
}
console.log('before `accept`');
server.accept();
console.log('after `accept`');
return new Response(null, {
status: 101,
webSocket: client,
});
}
);
If I use a websocket dev tool, like the online websocketking.com, I can connect to and send messages to that /media-stream endpoint just fine, over cloudflared.
Or if I swap out cloudflared for ngrok, that too works.
But with my ideal setup using cloudflared twilio says it never gets the 101 response. If I try to write to the client socket, Twilio gives a warning "received response other than 101". If I never write to the socket, just return the 101, it times out waiting for the handshake.
-
Tunnel ID : tunnelID=1d6b2701-227a-42aa-8df6-0cccde21d322
-
cloudflared config: I've tried many options, but none change the game at all, so here's my minimal setup:
tunnel: devtunnel
loglevel: trace
ingress:
- hostname: devtunnel.mysite.io
service: http://localhost:8787
- hostname: devtunnel.mysite.io
service: ws://localhost:8787
- service: http_status:404
Expected behavior
I expect to be able to stream audio bidirectionally with Twilio from a local machine, with a Cloudflare worker backend (wrangler dev) and a cloudflared tunnel.
Environment and versions
- OS: Ubuntu 22.04.5
cloudflared Version: 2025.4.2 (latest)
wrangler version: 4.14.4 (latest)
Logs and errors
I really regret that for all my debugging (over 4, now 5 days), I don't have good logs locally, just the Twilio responses:
- received response other than 101
- handshake timeout
Locally, nothing really gets reported. Eventually downstream things break when the ws closes from twilio's end - breaks immediately if I try to write to the client socket and get error 1) , or after 10 seconds (twilio's timeout threshold) if I merely respond with the 101 and don't write anything, error 2)
Again, ngrok works as a tunnel just fine. My suspicion is that cloudeflared is dropping responses under certain conditions, perhaps conditioned on the request headers from Twilio.
Describe the bug
I cannot connect a Twilio stream via websocket over
cloudflaredto mywrangler devbackend locally. This specific setup fails, but any other setup works fine!? I'm 50-60 hours into this bug, over 4 days, and my hair is all gray now :)TL;DR: 4 setups:
cloudeflaredlocally +wrangler devserver + Twilio websocket = Twilio error "31920: Stream - WebSocket - Handshake Error ... server didn't respond with 101"cloudeflared+wrangler dev+ manually connect to ws fromwebsocketking.com(no twilio)ngrok+ samewrangler dev+ same Twilio websocketcloudeflared+node server.js(rewritten, non-CF Worker server) + Twilio websocketTo Reproduce
With a simple Hono router setup in a CF Worker, the following tries to construct a minimal websocket, but never receives any messages over it because the handshake fails. Twilio calls
POST /incoming-calland receives a message to connect to the websocket on/media-stream. It tries, my server says it sent a101response, but Twilio says it never gets it (but the other setups do indeed work):If I use a websocket dev tool, like the online
websocketking.com, I can connect to and send messages to that/media-streamendpoint just fine, overcloudflared.Or if I swap out
cloudflaredforngrok, that too works.But with my ideal setup using
cloudflaredtwilio says it never gets the101response. If I try to write to the client socket, Twilio gives a warning "received response other than 101". If I never write to the socket, just return the101, it times out waiting for the handshake.Tunnel ID :
tunnelID=1d6b2701-227a-42aa-8df6-0cccde21d322cloudflared config: I've tried many options, but none change the game at all, so here's my minimal setup:
Expected behavior
I expect to be able to stream audio bidirectionally with Twilio from a local machine, with a Cloudflare worker backend (
wrangler dev) and acloudflaredtunnel.Environment and versions
cloudflaredVersion: 2025.4.2 (latest)wranglerversion: 4.14.4 (latest)Logs and errors
I really regret that for all my debugging (over 4, now 5 days), I don't have good logs locally, just the Twilio responses:
Locally, nothing really gets reported. Eventually downstream things break when the ws closes from twilio's end - breaks immediately if I try to write to the client socket and get error
1), or after 10 seconds (twilio's timeout threshold) if I merely respond with the 101 and don't write anything, error2)Again,
ngrokworks as a tunnel just fine. My suspicion is thatcloudeflaredis dropping responses under certain conditions, perhaps conditioned on the request headers from Twilio.