From f8cad44cd66869818bf1788ed3156116dbc9cb57 Mon Sep 17 00:00:00 2001 From: mcwigglesmcgee Date: Thu, 12 Feb 2026 05:55:00 -0800 Subject: [PATCH] fix(voice-call): pass Twilio stream auth token via instead of query string (#14029) Twilio strips query parameters from WebSocket URLs in TwiML, so the auth token set via ?token=xxx never arrives on the WebSocket connection. This causes stream rejection when token validation is enabled. Fix: pass the token as a element inside , which Twilio delivers in the start message's customParameters field. The media stream handler now extracts the token from customParameters, falling back to query string for backwards compatibility. Co-authored-by: McWiggles --- extensions/voice-call/src/media-stream.ts | 8 +++++++- extensions/voice-call/src/providers/twilio.ts | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/extensions/voice-call/src/media-stream.ts b/extensions/voice-call/src/media-stream.ts index 2525019cd43..ebb0ed9d844 100644 --- a/extensions/voice-call/src/media-stream.ts +++ b/extensions/voice-call/src/media-stream.ts @@ -146,6 +146,11 @@ export class MediaStreamHandler { const streamSid = message.streamSid || ""; const callSid = message.start?.callSid || ""; + // Prefer token from start message customParameters (set via TwiML ), + // falling back to query string token. Twilio strips query params from WebSocket + // URLs but reliably delivers values in customParameters. + const effectiveToken = message.start?.customParameters?.token ?? streamToken; + console.log(`[MediaStream] Stream started: ${streamSid} (call: ${callSid})`); if (!callSid) { console.warn("[MediaStream] Missing callSid; closing stream"); @@ -154,7 +159,7 @@ export class MediaStreamHandler { } if ( this.config.shouldAcceptStream && - !this.config.shouldAcceptStream({ callId: callSid, streamSid, token: streamToken }) + !this.config.shouldAcceptStream({ callId: callSid, streamSid, token: effectiveToken }) ) { console.warn(`[MediaStream] Rejecting stream for unknown call: ${callSid}`); ws.close(1008, "Unknown call"); @@ -393,6 +398,7 @@ interface TwilioMediaMessage { accountSid: string; callSid: string; tracks: string[]; + customParameters?: Record; mediaFormat: { encoding: string; sampleRate: number; diff --git a/extensions/voice-call/src/providers/twilio.ts b/extensions/voice-call/src/providers/twilio.ts index b1f03b21176..245c5e2bc3b 100644 --- a/extensions/voice-call/src/providers/twilio.ts +++ b/extensions/voice-call/src/providers/twilio.ts @@ -429,10 +429,21 @@ export class TwilioProvider implements VoiceCallProvider { * @param streamUrl - WebSocket URL (wss://...) for the media stream */ getStreamConnectXml(streamUrl: string): string { + // Extract token from URL and pass via instead of query string. + // Twilio strips query params from WebSocket URLs, but delivers + // values in the "start" message's customParameters field. + const parsed = new URL(streamUrl); + const token = parsed.searchParams.get("token"); + parsed.searchParams.delete("token"); + const cleanUrl = parsed.toString(); + + const paramXml = token ? `\n ` : ""; + return ` - + ${paramXml} + `; }