From 60b87826bbed38db44526377776bc2502d93a17c Mon Sep 17 00:00:00 2001 From: Ghost Date: Sun, 18 Jan 2026 20:20:53 -0800 Subject: [PATCH] Voice-call: fix Twilio status callbacks --- extensions/voice-call/src/providers/twilio.ts | 4 ++-- .../voice-call/src/providers/twilio/api.ts | 22 ++++++++++++++++--- 2 files changed, 21 insertions(+), 5 deletions(-) diff --git a/extensions/voice-call/src/providers/twilio.ts b/extensions/voice-call/src/providers/twilio.ts index 62a65eb4c..6e68556e3 100644 --- a/extensions/voice-call/src/providers/twilio.ts +++ b/extensions/voice-call/src/providers/twilio.ts @@ -389,12 +389,12 @@ export class TwilioProvider implements VoiceCallProvider { // Build request params - always use URL-based TwiML. // Twilio silently ignores `StatusCallback` when using the inline `Twiml` parameter. - const params: Record = { + const params: Record = { To: input.to, From: input.from, Url: url.toString(), // TwiML serving endpoint StatusCallback: statusUrl.toString(), // Separate status callback endpoint - StatusCallbackEvent: "initiated ringing answered completed", + StatusCallbackEvent: ["initiated", "ringing", "answered", "completed"], Timeout: "30", }; diff --git a/extensions/voice-call/src/providers/twilio/api.ts b/extensions/voice-call/src/providers/twilio/api.ts index 5cf9cfd28..9fcb202a8 100644 --- a/extensions/voice-call/src/providers/twilio/api.ts +++ b/extensions/voice-call/src/providers/twilio/api.ts @@ -3,16 +3,33 @@ export async function twilioApiRequest(params: { accountSid: string; authToken: string; endpoint: string; - body: Record; + body: URLSearchParams | Record; allowNotFound?: boolean; }): Promise { + const bodyParams = + params.body instanceof URLSearchParams + ? params.body + : Object.entries(params.body).reduce( + (acc, [key, value]) => { + if (Array.isArray(value)) { + for (const entry of value) { + acc.append(key, entry); + } + } else if (typeof value === "string") { + acc.append(key, value); + } + return acc; + }, + new URLSearchParams(), + ); + const response = await fetch(`${params.baseUrl}${params.endpoint}`, { method: "POST", headers: { Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`, "Content-Type": "application/x-www-form-urlencoded", }, - body: new URLSearchParams(params.body), + body: bodyParams, }); if (!response.ok) { @@ -26,4 +43,3 @@ export async function twilioApiRequest(params: { const text = await response.text(); return text ? (JSON.parse(text) as T) : (undefined as T); } -