Twilio voice call would immediately complete almost instantly. I know there is a similar question with accepted answer but not entirely revelant to my case and answer doesn’t work.
Here are a few things I have tried:
- Tried including the recipient number in
callDevice?.connect()
. Adding this would include the number in theTo
field for only ringing webhook event received. - Tried not including
dial.number(+2_RECEIPT_NUMBER);
.
Webhook & XML
Here is what the ringing
webhook received looks like:
{
ApplicationSid: 'APP_SID',
ApiVersion: '2010-04-01',
Called: '',
Caller: 'client:+13NUMBER_FROM_TWLIO',
CallStatus: 'ringing',
From: 'client:+13NUMBER_FROM_TWLIO',
CallSid: 'CALL_SID',
To: '', //would be present if number is included in the call.connect.
Direction: 'inbound',
AccountSid: 'MY_ACCOUNT_SID'
}
I would send an XML that looks like this in reponse:
<?xml version="1.0" encoding="UTF-8"?><Response><Dial callerId="+13NUMBER_FROM_TWLIO"><Number>+2_RECEIPT_NUMBER</Number></Dial></Response>
Then a completed
is receved:
{
ApiVersion: '2010-04-01',
Called: '',
CallStatus: 'completed',
Duration: '0',
From: 'client:+ 13NUMBER_FROM_TWLIO',
Direction: 'inbound',
CallDuration: '0',
Timestamp: 'Sat, 14 Dec 2024 19:06:32 +0000',
AccountSid: 'MY_ACCOUNT_SID',
CallbackSource: 'call-progress-events',
ApplicationSid: 'APP_SID',
Caller: 'client:+13NUMBER_FROM_TWLIO',
SequenceNumber: '0',
CallSid: 'CallSid',
To: ''
}
Code sample:
Here is what my code looks like:
- I first generate a token this way:
const twilioAccountSid = process.env.TWILIO_ACCOUNT_SID;
const twilioApiKey = process.env.TWILIO_API_KEY;
const twilioApiSecret = process.env.TWILIO_API_SECRET;
// Used specifically for creating Voice tokens
const outgoingApplicationSid = "APP_SID";
// const identity = 'user';
// Create a "grant" which enables a client to use Voice as a given user
const voiceGrant = new VoiceGrant({
outgoingApplicationSid: outgoingApplicationSid,
incomingAllow: true, // Optional: add to allow incoming calls
});
// Create an access token which we will sign and return to the client,
// containing the grant we just created
const token = new AccessToken(
twilioAccountSid,
twilioApiKey,
twilioApiSecret,
{ identity: `+13NUMBER_FROM_TWLIO ` }
);
token.addGrant(voiceGrant);
// Serialize the token to a JWT string
const tokenJWT = token.toJwt()
console.log(tokenJWT);
return tokenJWT
Then with the return JWT token, I would create a device, and connect call:
const handleCall = async (phoneNumber) => {
// return toast.error(`You can't dial a number yet`)
const params = { To: phoneNumber };
callDevice?.emit("connect");
callDevice
?.connect()
// ?.connect({
// params: params,
// rtcConstraints: {
// audio: true,
// },
// })
.then((call) => {
setCallInstance(call)
call.on("accept", () => {
setConnection(connection);
setUserState(USER_STATE.ON_CALL);
console.log("call accepted");
});
call.on("disconnect", () => {
console.log("The call has been disconnected.");
setUserState(USER_STATE.READY);
setConnection(null);
});
call.on("reject", () => {
console.log("The call was rejected.");
});
});
};
Then for the webhook used in TwiML, I have:
const { ApplicationSid, ApiVersion, Called, Caller, CallStatus, From, To, CallSid, Direction, AccountSid } = req.body
console.log({ ...req.body })
if (CallStatus === "ringing" || CallStatus === "initiated") {
const client = new VoiceResponse.twiml.VoiceResponse();
const dial = client.dial({ callerId: `+13NUMBER_FROM_TWLIO ` });
dial.number(`+2_RECEIPT_NUMBER`);
// dial.number({
// statusCallbackEvent: 'initiated ringing answered in-progress completed',
// statusCallback: 'https://myapp.com/calls/events',
// statusCallbackMethod: 'POST'
// }, '+12349013030');
const clientXML = client.toString()
console.log({ clientXML })
res.setHeader("Content-Type", "text/xml");
res.send(clientXML);
}
if (CallStatus === "in-progress") {
// Twilio credentials
const client = twilio(accountSid, authToken);
const call = await client.calls(CallSid).fetch();
const price = parseFloat(call.price || 0);
const priceUnit = call.priceUnit;
const duration = call.duration;
console.log({ price, priceUnit, duration })
}
if (CallStatus === 'completed') {
const client = twilio(accountSid, authToken);
const call = await client.calls(CallSid).fetch();
const price = parseFloat(call.price || 0);
const priceUnit = call.priceUnit;
const duration = call.duration;
console.log({ price, priceUnit, duration })
// await saveCallCostToDatabase({
// callSid: CallSid,
// totalCost: price,
// duration: duration
// });
}
if (CallStatus === 'canceled') {
}
if (CallStatus === 'busy') {
}
if (CallStatus === 'failed') {
}
Expected behavior:
- The call rings on the recipient phone,
+2_RECEIPT_NUMBER
, after the XML response was sent to twilio. - An
in-progress
webhook is received once the recipient picks up. - A
completed
webhook is received when the call ends.
According to the Twilio doc here, there should be a ring at my destination number and completed calls incur charges. Neither of these happened, making it all the more confusing.
+13NUMBER_FROM_TWLIO
is twilio’s provided number.
+2_RECEIPT_NUMBER
is the number I am trying to call.
Been at this all day, any help will be greatly appreciated.