Trying to delay a second call until the first is answered, confused by results from "statusCallback" - twilio

Note: The code below works correctly. The cause of the behavior I was experiencing was because the agent phone number is a Twilio number that is immediately answered (transitioning the call status to In-progress) and forwarded to a SIP address. Once I put the SIP address directly in for the agent, the script works as expected.
I'm leaving this post here in case someone else has a similar issue where another process is causing unexpected behavior elsewhere in the code.
I'm working on a process that will accomplish the following:
Make an outbound (from Twilio) call to an agent and add the agent to a conference.
Wait for the agent to answer the call.
Deliver a "hint" message to the agent about the customer to be added to the conference.
Make an outbound call to the customer, and add the customer to the conference.
The C# .NET code I have, which works for steps 1 and 4, is:
// Initiate First Call
var call = CallResource.Create(
to: new Twilio.Types.PhoneNumber( "+" + agent ),
from: new Twilio.Types.PhoneNumber( "+" + callerid ),
callerId: callerid,
twiml: new Twilio.Types.Twiml(
"<Response><Dial answerOnBridge='true' hangupOnStar='true'>"
+ "<Conference startConferenceOnEnter='true' beep='false'>"
+ agent+member + "</Conference>"
+ "</Dial></Response>" ),
statusCallback: new Uri( "https://callrouting-2700.twil.io/conference" ),
statusCallbackEvent: new List<string> { "initiated","ringing","answered","completed" },
statusCallbackMethod: Twilio.Http.HttpMethod.Get
);
// Initiate Second Call
call = CallResource.Create(
to: new Twilio.Types.PhoneNumber( "+" + member ),
from: new Twilio.Types.PhoneNumber( "+" + callerid ),
callerId: callerid,
twiml: new Twilio.Types.Twiml(
"<Response><Dial answerOnBridge='true'>"
+ "<Conference beep='false'>" + agent+member + "</Conference>"
+ "</Dial></Response>" )
);
To implement steps 2 and 3 above, I assumed that I could move the second call initiation to a separate function called by the statusCallback URL. In this script, I would check the CallStatus and add the second call when the first call's status changes to In-progress.
The problem I'm having is that the call status of the first call quickly moves from Initiated to Ringing to In-progress even before the agent answers the call.
2023-02-02 19:58:29 UTC Execution started...
2023-02-02 19:58:29 UTC Initiated
2023-02-02 19:58:29 UTC Execution ended in 60.63ms using 98MB
2023-02-02 19:58:29 UTC Execution started...
2023-02-02 19:58:29 UTC Ringing
2023-02-02 19:58:29 UTC Execution ended in 2.46ms using 102MB
2023-02-02 19:58:30 UTC Execution started...
2023-02-02 19:58:30 UTC In-progress
2023-02-02 19:58:30 UTC Execution ended in 2.45ms using 102MB
I assume the issue is that I can't add answerOnBridge='true' to the CallResource.Create, which will delay the In-progress status until the call is answered.
Is there a solution to delaying the second call until the first is answered?

See the note at the beginning of the post for resolution.

Related

How to get a callback when the agent is connected to the customer waiting the queue?

I have a simple setup where there's 1 queue and a few agents that are managed on my side with the help of PHP.
When the customer calls, the call gets enqueued like so:
$response = new VoiceResponse();
$response->enqueue('support', ['waitUrl' => 'wait-music.xml','action' => 'queue-action.php']);
$call = $client->account->calls->create($agent_number, $queue_number, [
"url" => 'queue.php',
]);
echo $response;
queue-action.php, in this case, is only called when the call is ended as it should do. Also, I start a call to an agent to connect him to the queue.
queue-action.php contents:
$response = new VoiceResponse();
$dial = $response->dial('',[
'action' => 'dial-callback.php',
]);
$dial->queue('support');
dial-callback.php here is also triggered after the call is ended.
My goal is to receive a callback when the calls are connected together, so I can mark a certain call as in-progress and assign an agent to it, to later know that this agent is busy on the line.
It feels like I would need to use statusCallbackEvent and statusCallback properties on $dial, but it's only available for <Dial><Number>, <Dial><Sip> and <Dial><Client>.
In other words, I want to record all queued calls in DB and updated their statuses accordingly (initiated / ringing / answered / completed) based on call status updates and assign relations with agents.
Would it be possible to accomplish it somehow using callbacks, without using TaskRouter?
Thank you
I got it working now, don't know how I missed it in the docs but the key is to use
$dial->queue('support', ['url' => 'queue-connect.php']);
the url parameter on Queue just in case someone will read this in the future.

How do I make my for await loop do everything within its body before moving on to the next line of code in twilio serverless functions?

I have been working on this problem for a couple of days now, and feel like I'm extremely close to solving the issue. I need to cycle through an object and send a text message to each record in the object before moving on to the next line of code. The code to send works when isolated locally. But I'm having trouble getting it to work in Twilio Functions. All my code appears to work, but the text messages are not sent.
for await (const contact of allItems) {
client.messages
.create({
body: "Hey " + contact.name + "!" + " " + message,
messagingServiceSid: messaging_service,
to: contact.key
});
};
// End Send Message to Each Contact
I've attached the portion of code I believe I'm having issues with.
What I want to do, is for this piece of code to run completely before moving on to the next few lines and invoking a callback.
Any idea how I could do something like this?
Twilio developer evangelist here.
The issue here is that you are not awaiting the actual result of the asynchronous call. Locally, that doesn't matter, but within Twilio Functions once you call the callback function all asynchronous calls that have started are terminated.
To create a loop that makes asynchronous requests and waits for them all to complete you will want to use Promise.all in conjunction with map. In your example, it should look like this:
function SendMessages(allItems, message, client, messagingService) {
const apiRequests = allItems.map((contact) => {
let usersName = contact.name;
return client.messages.create({
body: `Hey ${usersName}! ${message}`,
messagingServiceSid: messagingService,
to: contact.key,
});
})
return Promise.all(apiRequests);
}
In this function we map over the list contacts returning the promise that is created when we try to send a message. The variable apiRequests becomes an array of promises that represent the requests to the API. We can then use this array of promises in Promise.all. Promise.all will only resolve once all the promises it is passed have resolved (or if a promise rejects).
I recommend you read my answer to your previous question as that brings up some further issues that you might have with this.

Twilio PCI Compliant <Gather> in Function Widget with Studio

I've been stalking around here and have gotten most of my answers as I make my way through this new tool, but I'm now stuck and need some direct advice.
The Gather function in Studio is not PCI compliant, so I have to shift my call to a Function and return the parsed data--I finally figured out how to do that one--however, I've found that I cannot call the web service housed within the single function and had to send the with event.Digits to another function to make the web service call to my token provider. This works, however it has led to a strange result: my token is read back as TTS and then the call is hung up. I have no TTS action in play. Below are my sets of code:
Initial function called from Studio:
const got = require('got');
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.gather({
input: 'dtmf',
finishOnKey: '#',
timeout: 10,
action: 'paymenttest',
method: 'GET'
}).say('Enter CC');
console.log(twiml);
callback(null, twiml);
};
This successfully calls my function with the digits entered:
const got = require('got');
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.MessagingResponse();
const url ='my payment gateway' + event.Digits + '&EXPDATE=1220&CARDTYPE=VI';
got.get(url, {
headers: {
'content-Type': 'application/x-www-form-urlencoded'
}
}).then(function(response) {
// Check the response and ask your second question here
event.callback(null, response.body);
}).catch(function(error) {
// Boo, there was an error.
callback(error)
});
};
This successfully returns the token....but as mentioned prior...it's read back out to me instead of getting included in the data returned back to Studio.
Twilio developer evangelist here.
Right now Studio is not well setup for using TwiML from a Twilio Function and then continuing the flow. In your case when you return the token from your second Function Twilio is dealing with it as if you just returned text to a regular TwiML webhook. When this happens Twilio defaults to assuming you meant <Say> and reads out the text.
While the team work on redirecting calls back into Studio flows there is a workaround.
Instead of returning the token in the second Function, return some TwiML that includes a <Redirect> to your Studio flow's webhook URL with ?FlowEvent=audioComplete appended. You will also need to add a dummy Say/Play widget after your Function widget (it becomes the next part in the flow that can trigger an "audio complete" message, so exists to collect that and send on to the next widget).
The only thing that we haven't handled in thie workaround is sending the token to the flow. I don't believe we can do this via this redirect workaround, so instead I'd recommend storing the token in your own database or something like a Twilio Sync object. This way you can use it outside of Studio however you like. If you need it within the Studio flow then you can create one more Function that returns the token as JSON, and that will be stored within the flow variables then.
If you would prefer to use <Pay>, as this would be a lot easier, I also recommend requesting the pay connector you need.
I think philnash answer here got old, even when it still works.
Right now, you should have to call the first function using TwiML Redirect node.
In the second function, you should have to add a redirect to the webhook of the flow adding ?FlowEvent=return&foo=bar (where foo=bar should be changed by the info you really want to return).

Twilio Runtime <Say> and <Play> Verbs Ending Call

I am trying to write a simple function to play a recorded message and then say a dynamic message, but I'm running into some trouble... the function will play and say what I want it to, but then the call abruptly ends. Oddly, though, the flow log says that the call is still engaged... and when I go into the flow Steps, it tells me that the function event was a success and it transitioned to the next widget.
I tried testing the HelloVoice template function to see what would happen, and the same thing occurs. Does anyone have any ideas on how to make the call continue? Thanks!
My code:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
var play1 = event.play1; //recorded msg URL
var say1 = event.say1; //person-specific message
twiml.play(play1);
twiml.say(
{
voice: 'woman',
language: 'es',
},
say1);
callback(null, twiml);
For what its worth, there is this statement at the end in the troubleshooting section.
Studio User Guide
https://www.twilio.com/docs/studio/user-guide
Returning custom TwiML from an HTTP Request or Function widget isn't yet supported. It will be soon.
The reason your call ends is your TwiML ends

Forward Twilio call only during business hours

I've spent quite a few hours trying to find this and it still baffles me.
Am I able to only forward calls to my cell phone at certain hours, else voicemail? On the surface, this should be so simple - using a Twiml, perhaps? But I can't seem to "get it".
Thanks,
Nancy
Twilio Developer Evangelist here.
You can indeed do this with Twilio, but you will need to write some code and deploy it on the internet somewhere. Let's go through it.
Let's suppose that you're using this TwiML to forward your calls:
<Response>
<Dial>+5551234567</Dial>
</Response>
And you want to use the Say TwiML verb during 'out of hours' time:
<Response>
<Say>The office is currently closed.</Say>
</Response>
What you need is some application that can choose between them. For example, a simple Ruby and Sinatra application would look like this:
require 'sinatra'
post '/voice' do
content_type 'text/xml'
if Time.now.hour > 8 && Time.now.hour < 18
"<Response>
<Dial>+5551234567</Dial>
</Response>"
else
"<Response>
<Say>The office is currently closed.</Say>
</Response>"
end
end
Notice we're only looking at the time, not the day of the week. So you'll get calls sent between 8am and 6pm. You may want to make this a little more sophisticated depending on your needs.
You then just need to provide the URL for this application to Twilio. Depending on what tools you have available, you could run this on your own server, or on some cloud service provider such as Heroku, EngineYard, AppFog, etc. Most of these have very good documentation on how to deploy an application.
Hope this helps!
With Twilio functions this is easier now. You don't have to host anything anywhere. Just create a new Function linked to Incoming Voice Call events, then set your provisioned number to call that Function when a new call arrives. Here's a working example you can modify to fit your preferences; it includes dialing extensions but you can remove that if you want.
Note that it doesn't have DST logic and if you want that then you'll have to do some additional work with the moment() library.
https://gist.github.com/ChristopherThorpe/521bfdbd903ac7628ac01f8bb1d651a5
exports.handler = function(context, event, callback) {
const moment = require('moment');
//// Useful for debugging
//const util = require('util');
//console.log(util.inspect(context.getTwilioClient()));
//console.log(util.inspect(event));
// be sure to update numDigits below to match, or delete it for variable length
let phoneBook = {
"888" : "+1-800-800-8000", // super 8 motel
"666" : "+1-800-466-8356", // motel 6
"000" : null
};
let callerId = event.Caller; // || "+1-000-000-0000"; // default caller ID
let digits = event.Digits;
let twiml = new Twilio.twiml.VoiceResponse();
if (digits && phoneBook[digits]) {
twiml.say("Dialing extension " + digits);
twiml.dial({ callerId: callerId }, phoneBook[digits]);
twiml.hangup();
}
// Twilio time is in UTC. This allows 10 am to 7 pm PDT, or 9 am to 6 pm PST, weekdays.
// Twilio doesn't seem to have https://momentjs.com/timezone/ installed.
if ((moment().hour() >= 17 || moment().hour() < 2) && moment().isoWeekday() <= 5) {
let gather = twiml.gather({ numDigits: 3, timeout: 3 });
gather.say("Thank you for calling COMPANY NAME. Please dial your party's extension, or, hold, to leave a message.");
} else {
twiml.say("Thank you for calling COMPANY NAME. You have reached us outside business hours.");
}
twiml.redirect("http://twimlets.com/voicemail?Email=[YOUR-EMAIL]&Message=Please%20leave%20a%20message.&Transcribe=true");
callback(null, twiml);
};

Resources