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);
};
Related
I was able to implement basic Voice Conferencing but I feel my implementation may be lacking.
client = Client('ACxxxxxxxx', '34xxxxxxxxx')
#app.route('/', methods=["GET", "POST"])
def home():
form = ConferenceList() #form made using flask-wtf
if form.validate_on_submit():
contact_1 = form.data['contact_1'] #callee1
contact_2 = form.data['contact_2'] #callee2
from_ = form.data['from_'] #caller
response = VoiceResponse()
with Dial() as dial:
if from_ == MODERATOR:
dial.conference(
'Conf',
start_conference_on_enter=True,
end_conference_on_exit=True
)
else:
dial.conference('Conf', start_conference_on_enter=False)
response.append(dial)
'''here I feel could be a bottleneck'''
add_user(contact_1, conference_name='Conf', label='laed#1')
add_user(contact_2, conference_name='Conf', label="consumer")
return Response(str(response), 'text/xml')
return render_template('hello.html', form=form)
def add_user(contact, conference_name, label):
participant = client.conferences(conference_name).\
participants.create(
label=label, #label for participant
beep='onEnter',
record=True,
from_='from_', #same as above
to=str(contact)
)
if __name__ == '__main__':
app.run(debug=True, port=8000)
Basically the submit button triggers the / endpoint and the conference starts.
I feel there could be an issue with this implementation as I plan on cleaning it up and pushing to production (salesperson can make a conference call to leads on the app). Is there something I could have done better?
The voice(one client-one client) utilizes the Twilio Voice SDK, it there a way I could tweak it for conferencing?
You only need to respond with TwiML to a webhook from Twilio. In this case it appears that you are responding with TwiML to your application's front end when a submit button is pressed.
So, you can drop all the TwiML:
#app.route('/', methods=["GET", "POST"])
def home():
form = ConferenceList() #form made using flask-wtf
if form.validate_on_submit():
contact_1 = form.data['contact_1'] #callee1
contact_2 = form.data['contact_2'] #callee2
'''here I feel could be a bottleneck'''
add_user(contact_1, conference_name='Conf', label='laed#1')
add_user(contact_2, conference_name='Conf', label="consumer")
return "whatever"
Since the settings you are trying to apply in the TwiML do not apply to the participants joining the conference, you need to adjust your add_user function to use them. In this case, the startConferenceOnEnter and endConferenceOnExit parameters for the moderator should be sent when you create the participant. It might be easier to write two methods, add_user and add_moderator, to make things clear:
def add_user(contact, conference_name, label):
participant = client.conferences(conference_name).\
participants.create(
label=label, #label for participant
beep='onEnter',
record=True,
from_='from_', #same as above
to=str(contact),
start_conference_on_enter=False
)
def add_moderator(contact, conference_name, label):
participant = client.conferences(conference_name).\
participants.create(
label=label,
beep='onEnter',
record=True,
from_='from_',
to=str(contact),
start_conference_on_enter=True,
end_conference_on_exit=True
)
Then call different functions for the different participants:
add_moderator(contact_1, conference_name='Conf', label='laed#1')
add_user(contact_2, conference_name='Conf', label="consumer")
When you call add_user or add_moderator it will make an API request and slow down your server response. If you wanted to offload those requests to a worker, that would make your response quicker. But for 2 API requests, it is likely not a problem.
One other thing you might want to consider is the consumer experience. If they answer the phone before your agent does, then they will be greeted with hold music. You might want to architect it so that the application calls the agent first and only once they have picked up it then dials the consumer. Just worth considering.
Edit
After further explanation, you are now telling me that you want to make the call from the browser using the Twilio Voice SDK for JS.
To make outbound calls with the JS SDK you need to create an access token which includes an outgoing application sid, which refers to a TwiML application. That TwiML application has a voice URL. When you place the call with the SDK, Twilio makes a webhook request to the voice URL of your TwiML app. Your application can perform actions and return TwiML to tell Twilio what to do with the call.
When you create the call with the JS SDK you can pass parameters to the call.
const device = new Device(token);
const call = await device.connect({
params: {
To: ["+15551234567", "+145557654321"]
}
});
Those parameters are sent with the webhook request to your TwiML App voice URL. You can then use a response very similar to your original code to respond here, because you need to return TwiML to the request from Twilio, and start calls to the other participants in the call.
#app.route('/conference', methods=["POST"])
def conference():
const numbers = request.form["To"]
response = VoiceResponse()
with Dial() as dial:
dial.conference(
'Conf',
start_conference_on_enter=True,
end_conference_on_exit=True
)
response.append(dial)
for number in numbers:
add_user(number, conference_name='Conf')
return Response(str(response), 'text/xml')
This code receives the To parameter, a list of numbers to dial into this conference, builds the TwiML response that will put the browser caller into a conference call, places outbound calls to the numbers to dial them into the conference and then returns the TwiML to Twilio. The dialler in the browser will start the conference and the other participants will arrive in the conference when they answer the phone.
In this case you don't make the request to your server yourself, you use the JS SDK to trigger the call and let Twilio make the request to your server. As mentioned in the comments, you may want to offload the API calls to create participants to a background job so that you can respond to the webhook request quicker, but that is beyond the scope of this answer.
I have a Twilio phone number configured to direct inbound calls to a PHP webhook. The webhook uses some of the addon information to try and find a useful caller name. I'm also using Twilio's built-in CNAM lookups, but they don't work right in Canada (I always get the caller's number as their name).
The webhook is designed to forward calls to a Twilio SIP Domain first, where I expect I'll be answering most of the calls. Other calls, if deemed urgent, will be forwarded via PSTN.
I've reached the point where I can pull out a relevant name, but I'm having difficulty trying to forward that information to my FXS (HT802). As per the device's documentation:
http://www.grandstream.com/sites/default/files/Resources/ht80x_administration_guide.pdf
Auto: When set to “Auto”, the HT801/HT802 will look for the caller ID in the order of P-Asserted Identity Header, Remote-Party-ID Header and From Header in the incoming SIP INVITE
I'm not able to find a means to pass these headers via a SIP noun in TwiML. Based on Twilio's documentation:
https://www.twilio.com/docs/voice/twiml/sip#custom-headers
UUI (User-to-User Information) header can be sent without prepending x-
https://www.twilio.com/docs/voice/api/sending-sip#sip-x-headers
If you send headers without X- prefix, Twilio will not read the header. As a result, the header will not be passed in the output.
For context, here's a reduced snippet of the PHP code I'm using so far. Note: I'm not actually doing anything with the $callerName value yet.
<?php
// Simple "starting value", in case we can't resolve the name.
// (will also resolve the numbers used for unknown/blocked IDs)
$callerName = FriendlyFormatPhoneNumber($_POST['From']);
use Twilio\Twiml;
$addOns = null;
if (array_key_exists('CallerName', $_POST)) {
$callerName = $_POST['CallerName'];
} elseif (array_key_exists('AddOns', $_POST)) {
$addOns = json_decode($_POST['AddOns']);
$teloName = $addOns->results->telo_opencnam->result->name;
// If we pulled a telo name, and it doesn't seem to be a phone number
// (in case that could happen), use the telo name.
if (isset($teloName) && preg_match('/.*[0-9]{4,}, $teloName') == 0) {
$callerName = $teloName;
}
}
$response = new TwiML;
$dialParams = array(,
'timeout' => 20,
'hangupOnStar' => false,
'answerOnBridge' => true,
'action' => API_BASE_URL . '/dial-callback.php'
);
$dialer = $response->dial($dialParams);
$dialer->sip('sip:101#mytwiliodomain.sip.us1.twilio.com;transport=tls');
echo $response;
Long story short: How do I pass a custom caller name to my SIP devices using TwiML and the Twilio SIP Domains? I don't want to overwrite the number, just the name. And only on the inbound calls to the devices registered to my Twilio SIP domain.
In case it helps: Don't worry about translating to PHP if that's not your field; I can translate from TwiML :)
Unfortunately, this is not possible with Twilio SIP Domains. Currently, there is no way to set the Caller Name via TwiML.
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).
I am trying to use Twilio to create a conference call where it will be recorded. The idea is that I will merge my call with my Twilio number and it will start recording. However, I was wondering if it was possible to add restrictions.
So recording will turn on if certain numbers are on the phone, which I can add or delete.
Below is my working code that can record any calls regardless of their number.
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Say>Your call is being recorded</Say>
<Dial record="record-from-answer-dual" trim="trim-silence">
<Conference waitUrl="">
Conference Room
</Conference>
</Dial>
</Response>
TwiML Bins are great but sometimes, they wont work since you need to implement conditional logic to make things happen.
Twilio has a product called Twilio Functions, which is in Beta, which allows you to implement conditional logic and not have to spin up your own web servers to host that logic. Sort of like TwiML bins on steroids.
You can tweak the code below, to meet you needs. It nearly does what you want but use it more as an example of what you can do, and work from there. It is written in Javascript/Node.js. You would just need to update the logic to include TwiML which records the conference - https://www.twilio.com/docs/voice/twiml/conference#record.
You would then do the follow.
Go to Twilio Functions in your browser - https://www.twilio.com/console/runtime/functions/manage
Click the Red "+"
click + Blank for a Blank Template and click Create
Give the Function a Friendly Name, like ConferenceConditionalRecord or something similar you like
Give the Function a path, for example /conference
Erase the code in the Code box and cut/paste the code below, customizing it to meet your needs and click save
Visit you incoming numbers, https://www.twilio.com/console/phone-numbers/incoming
Choose Your Twilio Number
Under Voice & Fax, A Call Comes In, choose Function and then your Function and click Save
Test it out by calling in from different numbers. Only the 3 below will start the conference.
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
let callerId = event.From || null;
let conferenceParams = {};
let conferenceName = "My Example Conference Room";
let moderators = ["+18131234567", "+18131234568", "+18131234569"];
conferenceParams.beep = true;
if (moderators.indexOf(callerId) === -1) {
conferenceParams.startConferenceOnEnter = false;
conferenceParams.endConferenceOnExit = false;
}
else
{
conferenceParams.startConferenceOnEnter = true;
conferenceParams.endConferenceOnExit = true;
}
twiml.dial().conference(conferenceParams, conferenceName);
callback(null, twiml);
};
With Twilio I'd like to implement a service that sends inbound calls to a voice recording during specific times of day.
Can Twilio provide this behavior?
Twilio developer evangelist here. You can absolutely do that! I'm not sure what language or framework you're using, but here's an idea of how you'd accomplish this.
When Twilio receives a call on a Twilio number it makes a webhook request to your server to respond and tell it what to do with the call. The instructions are built in XML (TwiML). So, to do time based stuff you probably want to do something like this (my example is in Ruby using Sinatra as a web framework):
post '/call' do
time = Time.now
content_type 'text/xml'
response = "<Response>"
if out_of_hours?(time)
response = "<Say>Please leave a message</Say><Record />"
else
response = "<Dial><Number>YOUR_PHONE_NUMBER</Number></Dial>"
end
response = "</Response>"
response
end
You can then define out_of_hours? to follow the rules you want.
I hope this help, please let me know if you have any other questions.
Here is cakePhp - twilio url is xyz.com/Aptw/dialMtmTextJobLine/ - see the view function slap in the middle of the example.
<?php
App::uses('AppController', 'Controller');
class AptwController extends AppController {
public function beforeFilter() { $this->Auth->allow(); }
public $forwardIn = '<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Sip>';
public $forwardOut = '</Sip></Dial></Response>';
public function dialMtmTextJobLine() {
date_default_timezone_set("America/New_York");
$t=date("H:i",time());if ($t>"09:00" && $t<"16:00") {$this->dial103(); }
else { $this->dial2223(); }
}
public function dial103() { die($this->forwardIn.'sip:103#myast.com' . $this->forwardOut); }
public function dial2223() { die($this->forwardIn.'sip:2223#myast.com' . $this->forwardOut); }
}
Between 9am and 4pm, when twilio fetches the xml from your app, it sees one thing, and outside of that it sees another. Twilio is amazing, but it is meant to be just part of the answer, and I respect that.
Watch your formatting on the xml, Twilio is particular. I had tried to scrunch everything on one line and Twilio would not parse it.
This example does not properly use view templates, I get that. It's not entirely cakey, instead of dying, and instead of using the class variables to contain the xml boilerplate, I'll end up moving that to a view, making this code even shorter.
In fact, dial103 could be rendered with a view with no php inside it.
But better yet, direct dial urls for Twilio could look like xyz.com/Aptw/dialx/103 for my purposes. In cakephp you parse the next argument after controller Aptw and action dialx like thus: function dialx($extension) ... and the 103 automagically jumps into the $extension variable. Then you set a variable for the view, that's cakey.