Twilio-Client always returns connection status as open on outbound call even if the recipient has not accepted the call yet - twilio

I am trying to get the status of outbound call made through Twilio Client(js) v 1.10.1. But the .status() method returns the value as "open" even if the call is in ringing state. I tried adding enableRingingState: true as a parameter while initializing the Twilio.Device and also set the answerOnBridge="true" while creating the TwiMl Dial verb at server side.
As per the official documentation, js sdk should be returning these status "pending", "connecting", "ringing", "open", "closed".
This is required to showing "calling..." until the call is picked and start the timer(mm:ss) in my dialer in browser as soon the call is picked.
Note: Tried the same with Twilio's Quickstart guide code as well.
/**** Pseudo Code is like below ***/
// FRONT END CODE
let device = require('twilio-client').Device;
let outboundCall = '';
const twilioInit = () => {
// axios call to get capability {token} from service
device = device.setup(token,
{
allowIncomingWhileBusy: true,
enableRingingState: true
});
device.ready(function () {
console.log("Twilio ready");
device.identity = "Ratan";
console.log("Twilio Device ", device);
});
device.error(function (error) {
console.log("error in twilio ", error);
if (error.message == "JWT Token Expired") {
alert("token expired");
}
// If token has expired, dispatch an action to get new token.
});
}
// on phone icon click i am calling the below method
const makeOutboundCall = () => {
outboundCall = device.connect({
To: '+911234567890' // This is not the real phone number, I use number from list of Twilio verified numbers
});
outboundCall.on('ringing', function (hasEarlyMedia) {
console.log("ringing");
});
// Then tracking the status of this outboundCall object in setInterval function with a interval of one second by outboundCall.status() and clearing the..
// ..setInterval once the call disconnects
}
// BACK END CODE - SERVER SIDE (WEBHOOK)
#RequestMapping(value="/callCustomers", produces= "text/xml")
public String callByBrowserToMobile(#RequestParam String ApplicationSid, #RequestParam String ApiVersion,
#RequestParam String Called, #RequestParam String Caller,
#RequestParam String CallStatus, #RequestParam String To,
#RequestParam String From, #RequestParam String CallSid,
#RequestParam String Direction, #RequestParam String AccountSid) {
Number number = new Number.Builder(To).build();
Dial dial = new Dial.Builder().answerOnBridge(true).number(number).callerId(myTwilioNumber).build();
VoiceResponse response = new VoiceResponse.Builder().dial(dial).build();
logger.info(response.toXml());
return response.toXml();
}
The call is placed correctly but the issue is only with tracking outbound call status with twilio js library so that i can start call timer in UI once the call is picked by recipient.

Twilio developer evangelist here.
Edit: Found out what the issue was
When using <Dial answerOnBridge="true"> to make the ringing events available, the <Dial> needs to be the first TwiML that Twilio sees when it starts the call. When you are using a free trial account Twilio plays a message at the start of the call which acts as the first TwiML that is seen, so the answerOnBridge attribute doesn't take effect and you don't get the ringing events as you'd expect.
To fix this particular issue you will need to upgrade your account so that Twilio no longer reads out the trial account message at the start of the call.
Original answer:
I wouldn't use setInterval to poll the status of the call. Instead, I would listen to the events on the connection object. So your makeOutboundCall function should look a bit like this:
const makeOutboundCall = () => {
outboundCall = device.connect({
To: TO_NUMBER
});
outboundCall.on("ringing", (hasEarlyMedia) => {
console.log("The call has started and the other phone is ringing.");
});
outboundCall.on("accept", (connection) => {
console.log("The other person answered the phone!");
});
outboundCall.on("disconnect", () => {
console.log("The other person hung up.");
});
}
Let me know if that helps at all.

Related

Twilio Redirecting the wrong call when using CallSid and trying to update live call

So here's the logic of what I am working on:
Someone calls my Twilio Number I use the dial twiml to forward to a cell phone
I use gather and play a whisper to the operator answering the phone (so his cell phone).
The operator has a choice - press 1 to accept, press 2 (transfers to
a different agent).
step 3 is where I am having trouble I am using the code below:
$call = $twilio->calls($CallSid)
->update([
"method" => "POST",
"url" => "http:www.example.com/directcall.php"
]
);
Here's the problem it is modifying the call but it's redirecting the operators phone number instead of the person who is calling in. So the operator is getting redirected to the other operator and the customer is being hung up on. I tried using the parentcallsid too but that doesn't seem to work either.
Any ideas what I am doing wrong?
so just to be clear I want the flow to work like this:
Customer calls phone number -> redirects to designated operator -> if designated operator presses 2 it redirects the customer to operator 2 and disconnects operator 1 from the call. Is this possible?
Thanks for the help, I greatly appreciate it.
UPDATE PLEASE FIND THE CODE SAMPLES BELOW
Index.php
<?php
include ("config.php");
require_once './vendor/autoload.php';
use Twilio\TwiML\VoiceResponse;
$response = new VoiceResponse();
$twilionumber = ltrim($_POST['To'], '+');
$callernumber=ltrim($_POST['From'], '+');
createCall($phonenumbertouse,$response,$twilionumber);
echo $response;
function createCall($phonenumbertouse,$response,$twilionumber) {
$dial = $response->dial('',['timeout' => '30']);
$dial->number($phonenumbertouse, ['action' => "http://example.com/whisper.php",'method' => 'GET']);
}
WHISPER.PHP
<?php
include ("config.php");
require_once './vendor/autoload.php';
use Twilio\TwiML\VoiceResponse;
$response = new VoiceResponse();
$gather = $response->gather(['action' => "http://example.com/route.php",
'method' => 'GET']);
$gather->say($whisper, ['voice' => 'woman', 'language' => 'en-US']);
echo $response;
?>
route.php
<?php
include ("config.php");
require_once './vendor/autoload.php';
use Twilio\TwiML\VoiceResponse;
use Twilio\Rest\Client;
$response = new VoiceResponse();
$keyedInput=$_REQUEST['Digits'];
$mycallsid=$_REQUEST['ParentCallSid'];
if ($keyedInput == 1){
$response->say('connecting the call');
}
elseif ($keyedInput == 2){
$twilio = new Client($sid, $token);
$call = $twilio->calls($mycallsid)
->update([
"method" => "POST",
"url" => "http://example.com/redirect.php"
]
);
}
elseif ($keyedInput == 3){
$response->say('you selected 3');
}
else {
$response->say('Sorry, I don\'t understand that choice.');
}
echo $response;
?>
**Redirect.php **
<?php
include ("config.php");
require_once './vendor/autoload.php';
use Twilio\TwiML\VoiceResponse;
$response = new VoiceResponse();
$dial = $response->dial('+14151234567',['timeout' => '30']);
echo $response;
?>
Twilio developer evangelist here.
Your issue here is that you are using the wrong CallSid to update.
In this case there are two CallSids at play. When your user dials in to your Twilio number, that call between the user and Twilio has one CallSid. Then, when Twilio creates an outbound call to the operator, that call has a different CallSid.
In your application, when you get the <Gather> response from the operator, the CallSid being sent to your endpoint is the CallSid of the operator's call leg. Instead, you need to find the CallSid of the original call.
You should find that the ParentCallSid parameter is sent to the webhook endpoint as well. That ParentCallSid is the original inbound call's Sid and is what you should use to redirect the caller to another operator.
Edit
OK, so I had a go at building this. I don't normally work in PHP, so I wrote this in Node.js (as Twilio Functions). I got it to work and the answer still seems to me to be "use the ParentCallSid, so hopefully it gives you some idea where you might have gone wrong.
The incoming call
This makes an outbound call to my cell phone number, with a url set to make the whisper to me when I answer the call.
exports.handler = function (context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
const dial = twiml.dial();
dial.number({ url: "/whisper" }, MY_CELL_PHONE_NUMBER);
callback(null, twiml);
};
<Response>
<Dial><Number url="/whisper">MY_CELL_PHONE_NUMBER</Number></Dial>
</Response>
The whisper
This returns a <Gather> spoken to the person answering the cell phone number. It offers a choice, dial "1" to connect or "2" to hang up, triggering the original caller to dial to another number.
exports.handler = function (context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
const gather = twiml.gather({ digits: 1, action: "/after-whisper" });
gather.say("Dial 1 to connect, dial 2 to hang up.");
callback(null, twiml);
};
<Response>
<Gather action="/after-whisper" digits="1">
<Say>Dial 1 to connect, dial 2 to hang up.</Say>
</Gather>
</Response>
The Gather action "/after-whisper"
This checks the Digits parameter. If it is "1" it immediately calls back with an empty response, signalling the end of the whisper and leading the call to connect with the original caller.
If the Digits parameter is not "1" then we initialise a Twilio client and make a request to the API to update the call with the sid ParentCallSid to a new URL. Once that is complete we return TwiML that says to <Hangup> the whisper call (which wasn't strictly necessary, but I like the intention).
exports.handler = async function (context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
if (event.Digits === "1") {
callback(null, twiml);
} else {
const client = context.getTwilioClient();
try {
// Update the parent call with a new URL.
await client.calls(event.ParentCallSid).update({
url: URL_TO_NEXT_TWIML,
});
} catch (error) {
console.log(error);
}
twiml.hangup();
callback(null, twiml);
}
};
The next TwiML
This is the endpoint that the url above relates to, when it gets the callback it just returns a <Dial> to another number, though it could include another whisper and go round this loop again if desired.
exports.handler = async function (context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
twiml.dial(NEXT_NUMBER_TO_DIAL);
callback(null, twiml);
};
<Response>
<Dial>NEXT_NUMBER_TO_DIAL</Dial>
</Response>

how to get dynamically the caller phone number and pass it as one of JSON key in Twilio run function wdget?

I am new in Twilio and trying to develop an IVR that at some point runs a function (Run Fuuntion Widget). The function should send http request including the user phone number to a service provider to make payment for a product selected by the user.
I tryed the code bellow but the variable is not getting the user phone number.
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
var Client = require("#paymentsds/mpesa").Client;
var phoneNumber = event.From
var phoneNumberNorm = phoneNumber.substring(4);
var client = new Client({
apiKey: 'xxxxxxxxxxxxxxxxxx', // API Key
publicKey: 'xxxxxxxxxxxxxxxxxx', // Public Key
serviceProviderCode: '171717' // input_ServiceProviderCode
});
var paymentData = {};
paymentData['from'] = phoneNumberNorm;
paymentData['reference'] = '11114';
paymentData['transaction'] = 'T12344CC';
paymentData['amount'] = '10';
client.receive(paymentData).then(function(r) {
// Handle success scenario
twiml.say("Your payment was successfull");
callback(null, twiml);
}).catch(function(e) {
// Handle success scenario
twiml.say("Your payment failed");
callback(null, twiml);
});
};
Twilio developer evangelist here.
You will need to pass the phone number into the Function via the Run Function widget.
You need to add a Function Parameter, for example From, and then the data you want to pass, which for the incoming phone number is {{contact.channel.address}}. Like so:
Then you can use event.From in your Function code.

Twilio Function to divert unanswered calls or calls received outside office hours to voicemail, otherwise dial sip

I already had this function in my Twilio Functions section, although I'm not sure if I copy-pasted this from somewhere a while ago or if it came by default:
var moment = require('moment-timezone')
exports.handler = function(context, event, callback) {
let now = moment().tz('Australia/Brisbane');
let isWorkday = (now.isoWeekday() < 6);
let isWorkingHour = (now.hour() > 7 && now.hour() < 17);
let response = {};
if(isWorkday && isWorkingHour) {
callback(null, response);
} else {
callback("Service is closed");
}
};
I also found another SO post where someone included a basic function to divert to voicemail if a call is unanswered:
exports.handler = function(context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
if (event.DialCallStatus === 'completed' || event.DialCallStatus === 'answered') {
twiml.hangup();
} else {
twiml.say("Service is closed");
twiml.record({
transcribe: true,
transcribeCallback: "http://twimlets.com/voicemail?Email=example#domain.com",
action: "/hangup"
});
}
callback(null, twiml);
};
What I want to do is basically combine these two so that:
If call is received where !isWorkday || !isWorkingHour then send straight to voicemail. Don't ring the phone at all.
If call is receive where isWorkday && isWorkingHour then run something like this Twiml bin:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Sip>
me#myuniqueid.sip.us1.twilio.com;region=au1
</Sip>
</Dial>
</Response>
If the call is not answered within 20 seconds then send to voicemail (with a different greeting to step 1).
Bonus question: I obviously also need to be able to listen to the voicemail as I doubt the transcribing will be very accurate. Is there any way to include a link to the voicemail (or the voicemail mp3 itself) in the email I receive when a new voicemail is created? Or can I create a function/twiml bin for outgoing calls that would let me dial a number and listen to my voicemails, like how normal voicemail works??
Heyo, Twilio Developer Evangelist here. 👋
I just set down and built your use case in a single function. There is a lot going on inside of this one function (and I might recommend splitting the work into several functions).
I'm happy to answer your bonus questions, too, but please open it separately to keep this answer focused. :)
Let's have a look at a working example!
const moment = require('moment-timezone');
function isServiceOpen() {
let now = moment().tz('Australia/Brisbane');
let isWorkday = now.isoWeekday() < 6;
let isWorkingHour = now.hour() > 7 && now.hour() < 17;
return isWorkday && isWorkingHour;
}
exports.handler = function(context, event, callback) {
const twiml = new Twilio.twiml.VoiceResponse();
console.log(event);
const callIncludesRecording = !!event.RecordingUrl;
const callWasAnswered = event.DialCallStatus === 'completed';
const callWasNotAnswered = event.DialCallStatus === 'no-answer';
const serviceIsOpen = isServiceOpen();
if (callWasAnswered) {
twiml.hangup();
} else if (callIncludesRecording) {
console.log('Call includes recording!');
// do something with the recording URL here
console.log(event.RecordingUrl);
twiml.say("Thank you! We'll come back to you shortly.");
twiml.hangup();
} else if (callWasNotAnswered) {
console.log('Call was not answered...');
twiml.say(
'Unfortunately no one can answer right now. But you can leave a message.'
);
twiml.record({
action: '/handle-call'
});
} else if (!serviceIsOpen) {
console.log('Service is closed...');
twiml.say('Service is closed but you can leave a message');
twiml.record({
action: '/handle-call'
});
} else {
twiml.dial(
{
action: '/handle-call',
method: 'POST',
timeout: 5
},
'+4915...'
);
}
callback(null, twiml);
};
The function you see above is available under a /handle-call endpoint and answers all webhooks for a call.
Scenario 1 - a call is not answered
At the end of the function, you see the dial function call. The important piece for this case is that dial supports a timeout and an action attribute.
twiml.dial(
{
action: '/handle-call',
method: 'POST',
timeout: 30
},
'+49157...'
);
The above tells Twilio to try to call the number +49157... for 30 seconds (it's actually closer to 35 – you can read details in the docs). If the call ends or no one answered the call until the timeout is reached Twilio will ask the defined action URL for additional TwiML configuration.
The URL in the action attribute references the same function path (/handle-call) and the same function will be executed again but this time the event object will include a DialCallStatus of no-answer (have a look at the variable callWasNotAnswered). If the call was not answered you can return TwiML to say a message and tell the API to start the recording of the call.
// no one answered – let's record
twiml.say(
'Unfortunately, no one can answer right now. But you can leave a message.'
);
twiml.record({
action: '/handle-call'
});
The record verb also allows an action attribute which lets you define a URL that should be requested when the recording finished (we'll use again the same function under the same /handle-call endpoint).
The call to the same URL will then include a RecordingUrl inside of the event object. If this property is present you know that it is the result of the recording. At this stage, it is time to do something with the recording URL (send a message, log it, ...), to finish the call after saying "goodbye" and to hang up.
// do something with the recording URL here
console.log(event.RecordingUrl);
twiml.say("Thank you! We'll come back to you shortly.");
twiml.hangup();
The webhook flow is as follows:
POST /handle-call (initial webhook) -> dial +49157...
POST /handle-call (call after time out) -> say "unfortunately, ..." & record
POST /handle-call (recording finished) -> say "thank you" & hangup
Scenario 2 - the call is outside of business hours
For this scenario, I took the logic that you already provided and created a isServiceOpen helper function. When a call comes in outside of business hours the function responds with TwiML defining a message and a recording.
twiml.say('Service is closed but you can leave a message');
twiml.record({
action: '/handle-call'
});
After the finished recording the our function will be called (/handle-call). This time, the request will include a RecordingUrl and the handling will be the same as in scenario 1 (log the recording URL and hangup).
The webhook flow is as follows:
POST /handle-call (initial webhook) -> say "service is closed" & record
POST /handle-call (recording finished) -> say "thank you" & hangup
Scenario 3 – the call was answered
In case the call happens in business hours and was answered quickly enough there won't be a need for a recording. Because the dial verb includes an action attribute (we used this in scenario 1) another webhook will be sent after the call is ended.
This webhook will include a DialCallStatus parameter with the completed value. Then it's time to end the call and hang up.
twiml.hangup();
POST /handle-call (initial webhook) -> dial "+49157..."
POST /handle-call (call finished) -> hangup
I hope the above helps. 😊 As mentioned initially, it might be a good idea to split the functionality into /handle-call, /handle-recording and other functions.
Let me know if that helps and you have any further questions. :)

Twilio Functions Error 20429 - Too many requests multiple sms messages

I am using Twilio functions and Programable SMS to send SMS Messages to a list of numbers form my iOS App. There are just over 100 mobile numbers (113 on the time of this error) in the list. Most of these messages send but then the function says that it timed out after 502ms.
I am using the example code from Twilio to send to group messages (that I have copied below) and making a URLSession request from my iOS app.
Is there a way that I can fix this issue so that I can send to this fairly large list of phone numbers or make the function run for longer?
Thank you very much for your help.
Tom
Request:
let messagesPhoneNumberString = [+447987654321abc,+447123789456def,+447123456789ghi]
"https://myfunction.twil.io/send-messages?phoneAndID=\(messagesPhoneNumberString)&globalID=\(current globalID)"
My Twilio Function Code:
exports.handler = function(context, event, callback) {
let phoneAndIDString = event['phoneAndID'];
let globalID String = event['globalID'];
let numbersArray = phoneAndIDString.split(",");
Promise.all(
numbersArray(number => {
let phoneNumberSplit = "+" + number.slice(1, 13);
let idSplit = number.slice(13);
console.log('Send to number: ' + phoneNumberSplit + ' - with ID: ' + idSplit);
return context.getTwilioClient().messages.create({
to: phoneNumberSplit,
from: 'Example',
body: 'Hello World: ' + idSplit
});
})
)
.then(messages => {
console.log('Messages sent!');
callback(null, response);
})
.catch(err => console.error(err));
};
Twilio developer evangelist here.
Twilio Functions has a timeout of 5 seconds, so it is likely not the best idea to use a Twilio Function to send that many messages in one go.
You have some options though.
If you are sending all those numbers the same message then you could use the Twilio Notify passthrough API. Check out the details in this blog post about sending mass messages with Node.js.
Otherwise, if you have to send different messages then you could split up the numbers into batches and use the same function multiple times.
Finally, you could use a different platform to send the messages that doesn't have a 5 second limit.
In addition to the options provided in Phil's answer you could use recursion.
You could trigger the process from your app and pass all numbers in the initial function call just like you do now.
Then, the idea is to send just one message per function call and let the Twilio function call itself after it receives the response from .create(). This means no concurent calls to send messages, messages are sent one after another though the order in which they are received is not necessary the order in which the numbers are passed in the query string.
You'll need to add axios in the function dependencies configuration (https://www.twilio.com/console/runtime/functions/configure).
Axios is used to make the HTTP request to the function from within the function.
Each function run, tests for the stop condition which happens when the phone numbers query string length is zero. Then, uses .shift() to remove the first element from the numbers array to work with it. The remaining array is passed to the next function call.
This is the code I've tried, and it worked for me, but you'll have to change (the 11 length on .slice() method) for +44 because I've tested with US numbers +1 which are shorter in length.
exports.handler = function(context, event, callback) {
const axios = require("axios");
let phoneAndIDString = event["phoneAndID"].trim();
console.log(phoneAndIDString);
let globalIDString = event["globalID"].trim();
// stop condition for recursive call
if (phoneAndIDString.length === 0) {
return callback(null, true);
}
let numbersArray = phoneAndIDString.split(",");
console.log(numbersArray);
// take the first item of array
let number = numbersArray.shift();
console.log(number);
// the remaining array will be passed to the next function call
phoneAndIDString = numbersArray.join();
console.log(phoneAndIDString);
let phoneNumberSplit = "+" + number.slice(0, 11);
let idSplit = number.slice(11);
console.log("Send to number: " + phoneNumberSplit + " - with ID: " + idSplit);
context
.getTwilioClient()
.messages.create({
to: phoneNumberSplit,
from: "+17775553333",
body: "Hello World: " + idSplit
})
.then(msg => {
console.log("Message sent: " + msg.sid);
axios
.get(
"https://foo-bar-1234.twil.io/send-messages?globalID=baz&phoneAndID=" +
phoneAndIDString
)
.then(function(response) {
// handle success
console.log(response.status);
return callback(null, true);
})
.catch(function(err) {
// handle error
console.error(err);
});
})
.catch(function(err) {
console.error(err);
});
};
Try to go step by step with it while testing, console log things and then return early with return callback(null, true) as you go from top to bottom so you make sure you don't go in a loop.

putting an outgoing call from a twilio client to a phone on hold(Get phone side call Sid)

I am trying to implement 'hold call' functionality into my system. I have looked at this question and some twilio docs, and recommended way to do this is to dequeue the call, which will automatically play the hold music, and then retrieve from queue when hold is complete(un-hold).
This works perfectly fine, when I have an incoming call from a mobile, to my twilio client, and I can dequeue the call, and all works fine.
However when I do it with the outgoing call from the twilio client to the mobile, if I update the call to issue dequeue instruction, I get to hear the hold music on the client side itself, and the phone gets disconnected. I suppose this is because I have set the client leg of the call on hold. So the question is how do I get the call sid for the mobile leg of the call.
I have tried querying CallResource by ParentCallId, but that does not return anything in the case of outgoing calls. Any ideas?
Call is initiated on the client with:
var params = {
To: num
};
console.log('Calling ' + num + '...');
Twilio.Device.connect(params);
The connect API callback uses a simple Dial verb.
Client code for saving callid on connect:
Twilio.Device.connect(function (conn) {
if (conn._direction === 'OUTGOING') {
$scope.outgoing_call_sid = conn.parameters.CallSid;
$scope.number = conn.message.To;
} else {
$scope.incoming_call_sid = conn.parameters.CallSid;
$scope.number = conn.parameters.From;
}
$scope.message = 'In call with ' + $scope.number;
$scope.status = 'InCall';
});
Client code on hold button click:
$scope.hold = function () {
$scope.status = 'Hold';
$scope.message = 'Putting on hold...';
if ($scope.outgoing_call_sid) {
return $http.get(serviceBase + 'api/twilio/hold?callid=' + $scope.outgoing_call_sid);
}
};
Server side Hold API call:
public IHttpActionResult Hold(string callid) { /
//callid = GetLegCallId(callid); //Try to replace with child call
CallResource.Update(new UpdateCallOptions(callid) { Url = ConfigurationManager.AppSettings["ngrokUrl"] + "/api/twilio/enqueue", Method = HttpMethod.Get });
return Ok();
}
Code for getting any child calls:
public string GetLegCallId(string callId)
{
var calls = CallResource.Read(new ReadCallOptions() { ParentCallSid = callId });
if (calls.GetEnumerator().Current != null)
return calls.GetEnumerator().Current.Sid;
}
My bad. Twilio wasnt the issue. Issue was with usage of calls.GetEnumerator().Current != null.
Should do MoveNext on the Enumerator, before Current will have a value. Resolved by doing that. Stupid:(

Resources