IVR in TwiML; how do I set keypresses to dial particular numbers? - twilio

I'm trying to code an IVR in TwiML on the Twilio site. Ideally, what I would like to happen is:
Pressing "1" dials a number that I set.
Pressing "2" dials a number that I set.
Pressing "3" brings me to a directory. Nested within number "3", they are presented again with several choices, each of which dials a particular number.
I think I'm supposed to use action somewhere, but I am not sure how to set "if" conditions for dialing. My knowledge on coding is extremely rudimentary at best, but here is what I have set up so far.
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Pause length="2"/>
<Say voice="alice" language="en-gb">Thank you for calling our company, your number one source for commercial real estate listings.</Say>
<Gather numDigits="1">
<Say voice="alice" language="en-gb">Press 1 for Sales to Sign Up and Stand Out!</Say>
<Say voice="alice" language="en-gb">Press 2 for Support!</Say>
<Say voice="alice" language="en-gb">Press 3 for our company directory!</Say>
</Gather>
</Response>

You are right - you have to use action on . This action URL will get "DIGITS" that user pressed and then you can take appropriate action.
See a sample below (action="/abcxyzDemoIvr").
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Pause length="2"/>
<Say voice="alice" language="en-gb">Thank you for calling our company, your number one source for commercial real estate listings.</Say>
<Gather numDigits="1" action="<urlHere>/abcxyzDemoIvr">
<Say voice="alice" language="en-gb">Press 1 for Sales to Sign Up and Stand Out!</Say>
<Say voice="alice" language="en-gb">Press 2 for Support!</Say>
<Say voice="alice" language="en-gb">Press 3 for our company directory!</Say>
</Gather>
</Response>
Sample of how you can organise your action URL for <Gather> is also mentioned below :
app.post('/abcxyzDemoIvr',
function(i_Req,o_Res)
{
var ivrTwilRes = new twilio.TwimlResponse();
var iGathered=i_Req.body.Digits ;
if ( iGathered == 1)
{
ivrTwilRes.say("Action for Sales to Sign Up and Stand Out!");
ivrTwilRes.redirect( { method : 'GET' } , "/abcxyzDemoIvr_salesSignupAndStandOut" );
/*do your stuff here */
}
else if ( iGathered == 2 )
{
ivrTwilRes.say("Action for Support");
ivrTwilRes.redirect( { method : 'GET' } , "/abcxyzDemoIvr_support" );
/*do your stuff here */
}
else if ( iGathered == 3 )
{
ivrTwilRes.say("Action for CompanyDirectory");
ivrTwilRes.redirect( { method : 'GET' } , "/abcxyzDemoIvr_companyDirectory" );
/*do your stuff here */
}
else if ( iGathered == '*' )
{
ivrTwilRes.redirect( { method : 'GET' } , "/abcxyzDemoIvrMenu" );
}
else
{
ivrTwilRes.say("I'm sorry, that is not a valid choice. Please make a choice from the menu").redirect( { method : "GET" } );
}
ivrTwilRes.say("Thank you for calling my IVR . Goodbye.",
{
language:'en-gb',
voice : 'woman'
}
)
.pause({ length : 3 } )
.hangup();
o_Res.set('Content-Type','text/xml');
o_Res.send(ivrTwilRes.toString());
console.log("========================== Response Starts Here (for abcxyzDemoIvr post)============================");
console.log(o_Res);
console.log("========================== Response Ends Here (for abcxyzDemoIvr post)============================");
}
);
app.post('/abcxyzDemoIvr_CallAgent',
function(i_Req,o_Res)
{
var ivrTwilRes = new twilio.TwimlResponse();
var agentNum=i_Req.body.Digits ;
/*
console.log("========================== Request Starts Here (for abcxyzDemoIvr_CallAgent post)============================");
console.log(i_Req.body);
console.log("========================== Request Ends Here (for abcxyzDemoIvr_CallAgent post)============================");
*/
var whichAgent = /*your logic to get which number to dial */
ivrTwilRes.say("Connecting you to agent " + whichAgent )
.dial({callerId:'+447777777777',action:"/abcxyzDemoIvr_postCallHandler",method:"GET"},
function()
{
this.number('+44xxxxxxxxxx',{url:"/abcxyzDemoIvr_screenCaller",method:"GET"});
}
);
o_Res.set('Content-Type','text/xml');
o_Res.send(ivrTwilRes.toString());
}
);

Related

Voicemail calling back 20 seconds later

I'm trying to set up voicemail for a Twilio # (Not Twilio Cell)... And it works, for the most part, except I get called back 20 seconds later with nothing there (Step 5 Below). I think my issues are in TWIML 1/2 from testing, but I don't know.
I'll call in to my Twilo # from my cell.
I hit the reject on my cell (Even though my time out is 1, it doesn't give up.).
Leave the message
Hang up || press button to end
[~20 seconds after step 4] Get blank call back
[10-15 secs after step 5] Get my text / email as expected
Am I doing something wrong in TWIML 2?
or
Is using my cell for orig/term causing problems?
Incoming:
TWIML 1:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial action="https://-->TWIML 2"
timeout="1"
callerId="{{To}}"
>
<Number>##CELL##</Number>
</Dial>
</Response>
TWIML 2:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>
Please leave a message
</Say>
<Record action="https://TWIML 3"
method="POST"
maxLength="120"
playBeep="false"
trim="do-not-trim"
transcribe="true"
transcribeCallback="https://FUNCTION 1"
/>
<Say>I did not receive a recording.</Say>
</Response>
TWIML 3
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Bi</Say>
</Response>
FUNCTION 1
exports.handler = function(context, event, callback) {
var client = context.getTwilioClient();
const mailgun = require('mailgun-js')({apiKey: context.MG_KEY, domain:context.MG_DOMAIN});
var async = require('async');
var sendSMS = function(callback){
let dataSMS = {
to: '##CELL##',
from: event.To,
body: 'Voicemail from:\n ' + event.From + '\n\nText:\n' + event.TranscriptionText + '\n\n---MSG Recording---\n' + event.RecordingUrl
};
client.messages.create(dataSMS).then(function(response){
console.log('N [T1]: ' + response.status);
return callback(null, response);
}).catch(function(err){
console.log('T2: ' + err);
});
};
var sendEmail = function(callback){
let dataMG = {
to: '##TO##',
from: '##FROM##',
subject: 'Voicemail: ' + event.From,
text: event.TranscriptionText + '\n\n--- MSG Recording --->\n' + event.RecordingUrl
};
console.log('-------- Sending Mail (Mailgun API)---->' + dataMG.to);
mailgun.messages().send(dataMG, function (error, body) {
console.log('N [MB] Mailgun Body :' + body);
return callback(null, body);
});
};
var waitTime = function(callback){
setTimeout(function() {
return callback(200);
}, 1000);
};
var asyncTasks = [sendSMS, sendEmail, waitTime];
async.parallel(asyncTasks, function(err, result){
console.log('--------------------------DONE--------------------------' + result[0] + result[1]);
callback(null, 200);
});
};
Resolved:
So a couple days later and it is working as expected, without being phoned back ~20 seconds later. I don't think I changed anything, but who knows; now I'm having trouble forcing the unexpected behavior to occur :)
Below is an example of how to set up a voicemail system that will transcribe your message to a MMS including a link to the original voice message. This also includes an email copy, if you had a mailgun account set up.
There is an extra fee associated with using the transcription service, and you will have to delete the recordings eventually after you no longer want access to the recording.
If you are to try this out, when clean the code for you situation, the timeout variable in Twiml 1 is how many seconds it will ring for. Function 1 requires async, but you could write it without when tailoring for you situation.

Hunt Group for Twilio, using Twilio Functions. (aka FindMe )

I am trying to set up a hunt group with Twilio Twiml
Do I have to set up a different twimlbin for each number in the hunt group?
Or is there a way to join all this together into a single Twimlbin?
Twimlbin 1:
<Response>
<Dial
action="http://www.ourapp.com/webhook;FailUrl=/Twimlbin 2"
timeout="10"
callerId="555-555-5555">
NUMBER1
</Dial>
</Response>
Twimlbin 2:
<Response>
<Dial
action="http://www.ourapp.com/webhook;FailUrl=/Twimlbin 3"
timeout="10"
callerId="555-555-5555">
NUMBER2
</Dial>
</Response>
... Repeat N times for each agent ...
Thank you :-)
Twilio developer evangelist here.
TwiML Bins are great for static bits of TwiML, but your use case needs a bit more than that.
I recommend you check out Twilio Functions which allow you to run Node.js code in Twilio's infrastructure. I've built and tested a version of this that works with Twilio Functions.
Here's an explanation of how it works:
Start with an array of your numbers:
const numbers = [...];
Then, in the function, check if the callstatus is completed and hang up if it is.
exports.handler = function(context, event, callback) {
const response = new Twilio.twiml.VoiceResponse();
if (event.DialCallStatus === "complete" || event.finished) {
// Call was answered and completed or no one picked up
response.hangup();
} else {
If it's not, we work out the next number to call. If you have the next number in the URL. If you do save it to a variable, otherwise pick the first number in the array:
const numberToDial = event.nextNumber ? event.nextNumber : numbers[0];
Then, assign the next number to dial from the array.
let url;
const currentNumberIndex = numbers.indexOf(numberToDial);
if (currentNumberIndex + 1 === numbers.length) {
// no more numbers to call after this.
url = "/hunt?finished=true";
} else {
const nextNumber = numbers[currentNumberIndex + 1];
url = "/hunt?nextNumber=" + encodeURIComponent(nextNumber);
}
Then generate the TwiML to Dial the next number and pass the URL as the action. You can add your own URL as the statusCallbackUrl to keep a track of the statuses.
const dial = response.dial({ action: url });
dial.number({ statusCallback: "https://yourapp.com/statuscallback" }, numberToDial);
}
callback(null, response);
}
I can't promise this will work, but I hope you see where I'm going with it. Let me know if it helps at all.

Twilio,In Incoming calls when I queue the call second time parents call status gets completed

Twilio,In Incoming calls when I queue the call second time call status gets completed.But the hold music is still play on both side of the call.Here is my code to update calls.(First time hold unhold is working fine but when try to hold the second time is goes on the hold but when I fetch the call info by callsid the callstatus gets completed.)
function hold_call($parentCallSid,$action='hold'){
echo $parentCallSid;
$admin_id = get_current_user_id();
$childCalls = $this->client->calls->read(array("ParentCallSid" => $parentCallSid));
$childCallSid = $childCalls[0]->sid;
$rr = array(
"url" => "url/TwiML/hold_unhold.php?action=$action&admin_id=".$admin_id,
"method" => "POST"
);
// echo 'CallSid-> '.$childCallSid;
$call = $this->client->calls($childCallSid)->update($rr);
$call1 = $this->client->calls($parentCallSid)->fetch();
print_r($call);
print_r($call1);
//return $call->to;
}
function unhold_call($parentCallSid,$action='unhold'){
//echo $parentCallSid;
$admin_id = get_current_user_id();
$rr = array(
"url" => "url/hold_unhold.php?action=$action&admin_id=".$admin_id,
"method" => "POST"
);
//$call = $this->client->calls->read(array("ParentCallSid" => 'CA9631b87ec900aaa309f1847872cf0da3'));
$call1 = $this->client->calls($parentCallSid)->update($rr);
print_r($call1);
//return $call->to;
}
on hol_unhold.php
if($_REQUEST['action']=='hold'){
?>
<Response>
<Enqueue waitUrl="adHoldmusic.xml"><?php echo $_REQUEST['admin_id'] ?></Enqueue>
</Response>
<?php }else{
?>
<Response>
<Dial>
<Queue><?php echo $_REQUEST['admin_id'] ?></Queue>
</Dial>
<Play loop="0"><?php echo $hold_music;?></Play>
</Response>
<?php }
and for receiving call i m using this twiml-
<Response>
<Dial>
<Client statusCallbackEvent="initiated ringing answered completed" statusCallback="twilio_call_notification_handler.php" statusCallbackMethod="POST"><?php echo isset($numbers[$_REQUEST['Called']]) ? $numbers[$_REQUEST['Called']] : 'new'; ?></Client>
</Dial>
<Play loop="0"><?php echo $hold_music;?></Play>
</Response>

TwiML App unexpected end of call: Cannot find the declaration of element 'response'

I created TwiML application and set Voice request URL the web deployed ASP.NET-MVC aplication action method: http://voiceapp-001-site1.myasp.net/voice
This action method is invoked when somebody goes to the URL posted above:
public ActionResult Voice() {
Response.ContentType = "text/xml";
// put a phone number you've verified with Twilio to use as a caller ID number
string callerId = Settings.TwilioNumber;
// put your default Twilio Client name here, for when a phone number isn't given
string number = "jenny";
// get the phone number from the page request parameters, if given
if (Request["PhoneNumber"] != null) {
number = Request["PhoneNumber"];
}
// wrap the phone number or client name in the appropriate TwiML verb
// by checking if the number given has only digits and format symbols
string numberOrClient;
var m = Regex.Match(number, #"^[\d\+\-\(\) ]+$");
if (m.Success) {
numberOrClient = string.Format("<Number>{0}</Number>", number);
} else {
numberOrClient = string.Format("<Client>{0}</Client>", number);
}
ViewBag.CallerId = callerId;
ViewBag.NumberOfClient = numberOrClient;
return View();
}
The Voice view looks like:
<?xml version="1.0" encoding="UTF-8" ?>
<response>
<dial callerid="#ViewBag.CallerId">
#Html.Raw(ViewBag.NumberOfClient)
</dial>
</response>
Then I try to make test call:
but after 13 seconds call is automatically terminated and In the error log I get:
Notification SID NOe85ffe80dfc52e81f942a7414c9f7c9c
Warning 12200 Schema validation warning
Description Cannot find the declaration of element 'response'.
But below in the Body section I can see the element response:
Hi Twilio developer evangelist here.
Can you try modifying your view to look like this instead?
<?xml version="1.0" encoding="UTF-8" ?>
<Response>
<Dial callerId="#ViewBag.CallerId">
#Html.Raw(ViewBag.NumberOfClient)
</Dial>
</Response>
Remember XML is case-sensitive, so the casing in your XML tags actually matter. Also, I would suggest using the Twilio.TwiML helper library. With that, you can get your XML generated for you with the right casing, and avoiding typos altogether.
Here's how you'd do it:
var twiml = new TwilioResponse();
var dialAttributes = new {callerId = "48326304351"};
var dial = twiml.Dial("+15555555555", dialAttributes);
return TwiML(dial);

Charging if the recipient decline to answer twilio

**
<Response>
<Dial record="true" timeout="15" timeLimit="4257" callerId="+14589775871" action="http://demo.com/CallCharge.php?rid=81;4260" >
<Number url="http://demo.com/CallReceiver.php?name=Deval">+14582783238 </Number>
</Dial>
</Response>
**
In the above twiml,
*I call to 14582783238 number from this twilio number 14589775871
Receiver (14582783238) decline the call but it still it connected the called by 14589775871*
As per twilio rule
1) if receiver pickup the Call than it will go to this url "http://demo.com/CallReceiver.php?name=Deval" say message
2) if receiver decline the Call than it will not got to this url "http://demo.com/CallReceiver.php?name=Deval" but it will happen over their.
In second point, Twilio call not properly handle it or i doing somthing wrong here?
Please let me know as soon as possible about this matter.
http://demo.com/CallCharge.php is called at the end of the call, be it after a call or on hang-up.
Twilio will automatically pass the DialCallStatus, DialCallSid, DialCallDuration and RecordingUrl request parameters to your action URL.
<?php
/* CallCharge.php */
$DialCallStatus = isset($_REQUEST['DialCallStatus']) ? $_REQUEST['DialCallStatus'] : "";
$DialCallDuration = isset($_REQUEST['DialCallDuration']) ? $_REQUEST['DialCallDuration'] : "";
if($DialCallStatus != "completed") {
// Don't charge
} else {
// Charge $DialCallDuration
}
https://www.twilio.com/docs/api/twiml/dial

Resources