diff --git a/extensions/voice-call/src/webhook/realtime-handler.test.ts b/extensions/voice-call/src/webhook/realtime-handler.test.ts index 07107f08601..0f5076c4660 100644 --- a/extensions/voice-call/src/webhook/realtime-handler.test.ts +++ b/extensions/voice-call/src/webhook/realtime-handler.test.ts @@ -7,7 +7,7 @@ import { RealtimeCallHandler } from "./realtime-handler.js"; /** Extract the stream token from a TwiML body string. */ function extractStreamToken(twiml: string): string | null { - const match = twiml.match(/\?token=([^"&\s]+)/); + const match = twiml.match(/\/voice\/stream\/realtime\/([^"&\s]+)/); return match?.[1] ?? null; } @@ -99,7 +99,7 @@ describe("RealtimeCallHandler", () => { expect(payload.headers?.["Content-Type"]).toBe("text/xml"); expect(payload.body).toContain(""); expect(payload.body).toContain(" { @@ -112,7 +112,7 @@ describe("RealtimeCallHandler", () => { const req = makeRequest("/voice/webhook", ""); const payload = handler.buildTwiMLPayload(req); - expect(payload.body).toContain("wss://localhost:8443/voice/stream/realtime?token="); + expect(payload.body).toMatch(/wss:\/\/localhost:8443\/voice\/stream\/realtime\/[0-9a-f-]{36}/); }); it("embeds a unique token on each call", () => { diff --git a/extensions/voice-call/src/webhook/realtime-handler.ts b/extensions/voice-call/src/webhook/realtime-handler.ts index 56796828760..9eb7d4b2f21 100644 --- a/extensions/voice-call/src/webhook/realtime-handler.ts +++ b/extensions/voice-call/src/webhook/realtime-handler.ts @@ -61,7 +61,9 @@ export class RealtimeCallHandler { */ handleWebSocketUpgrade(request: http.IncomingMessage, socket: Duplex, head: Buffer): void { const url = new URL(request.url ?? "/", "wss://localhost"); - const token = url.searchParams.get("token"); + // Token is embedded as the last path segment (e.g. /voice/stream/realtime/) + // to survive reverse proxies that strip query parameters (e.g. Tailscale Funnel). + const token = url.pathname.split("/").pop() ?? null; const callerMeta = token ? this.consumeStreamToken(token) : null; if (!callerMeta) { console.warn("[voice-call] Rejecting WS upgrade: missing or invalid stream token"); @@ -123,7 +125,7 @@ export class RealtimeCallHandler { from: params?.get("From") ?? undefined, to: params?.get("To") ?? undefined, }); - const wsUrl = `wss://${host}/voice/stream/realtime?token=${token}`; + const wsUrl = `wss://${host}/voice/stream/realtime/${token}`; console.log( `[voice-call] Returning realtime TwiML with WebSocket: wss://${host}/voice/stream/realtime`, );