Voice-call: fix Twilio status callbacks

This commit is contained in:
Ghost
2026-01-18 20:20:53 -08:00
parent b04b51d2c4
commit 60b87826bb
2 changed files with 21 additions and 5 deletions

View File

@@ -389,12 +389,12 @@ export class TwilioProvider implements VoiceCallProvider {
// Build request params - always use URL-based TwiML. // Build request params - always use URL-based TwiML.
// Twilio silently ignores `StatusCallback` when using the inline `Twiml` parameter. // Twilio silently ignores `StatusCallback` when using the inline `Twiml` parameter.
const params: Record<string, string> = { const params: Record<string, string | string[]> = {
To: input.to, To: input.to,
From: input.from, From: input.from,
Url: url.toString(), // TwiML serving endpoint Url: url.toString(), // TwiML serving endpoint
StatusCallback: statusUrl.toString(), // Separate status callback endpoint StatusCallback: statusUrl.toString(), // Separate status callback endpoint
StatusCallbackEvent: "initiated ringing answered completed", StatusCallbackEvent: ["initiated", "ringing", "answered", "completed"],
Timeout: "30", Timeout: "30",
}; };

View File

@@ -3,16 +3,33 @@ export async function twilioApiRequest<T = unknown>(params: {
accountSid: string; accountSid: string;
authToken: string; authToken: string;
endpoint: string; endpoint: string;
body: Record<string, string>; body: URLSearchParams | Record<string, string | string[]>;
allowNotFound?: boolean; allowNotFound?: boolean;
}): Promise<T> { }): Promise<T> {
const bodyParams =
params.body instanceof URLSearchParams
? params.body
: Object.entries(params.body).reduce<URLSearchParams>(
(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}`, { const response = await fetch(`${params.baseUrl}${params.endpoint}`, {
method: "POST", method: "POST",
headers: { headers: {
Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`, Authorization: `Basic ${Buffer.from(`${params.accountSid}:${params.authToken}`).toString("base64")}`,
"Content-Type": "application/x-www-form-urlencoded", "Content-Type": "application/x-www-form-urlencoded",
}, },
body: new URLSearchParams(params.body), body: bodyParams,
}); });
if (!response.ok) { if (!response.ok) {
@@ -26,4 +43,3 @@ export async function twilioApiRequest<T = unknown>(params: {
const text = await response.text(); const text = await response.text();
return text ? (JSON.parse(text) as T) : (undefined as T); return text ? (JSON.parse(text) as T) : (undefined as T);
} }