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);
};
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.
Am trying to create a call center with Twilio, am almost there but now am stuck because i can't get custom passed parameters.
My main aim is to allow customers to call but first they should provide their emails and names first, then click call-customer button, i want to receive the custom parameters on the agent's side.
Now i try to pass the parameters but i can't retrieve them.
Here is my code to make and receive calls and to pass and get the parameters
(Customer side)This code allows outgoing calls, customers to make calls to the call-center agents
require __DIR__ . '/vendor/autoload.php';
use Twilio\Jwt\ClientToken;
$accountSid = '';
$authToken = '';
$appSid = '';
$capability = new ClientToken($accountSid, $authToken);
$capability->allowClientOutgoing($appSid);
$token = $capability->generateToken();
So according to the documentation i should pass the custom parameters like this:
var params = {"name": "John", "email": "john#gmail.com"};
Twilio.Device.connect(params);
(Agent side) This code allows incoming calls from customers to agents.
$accountSid = '';
$authToken = '';
$capability = new ClientToken($accountSid, $authToken);
$capability->allowClientIncoming('joey');
$token = $capability->generateToken();
In the agent side i use this code to receive customer information or custom parameters.
According to the documentation a code to get custom parameters is this:
if (connection.customParameters.hasOwnProperty("name")) {
let displayName = connection.customParameters.get("name");
console.log(displayName)
}
if (connection.customParameters.hasOwnProperty("email")) {
let customerID = connection.customParameters.get("email");
console.log(customerID)
}
but i get undefined
So when a customer calls this twilio function en-queues the call and assign it to an operator
here is the code:
exports.handler = function(context, event, callback) {
let twiml = new Twilio.twiml.VoiceResponse();
twiml.say(" Please hold, while we connect you to one of our available agent ");
twiml.enqueue({
workflowSid: context.WORKFLOW_SID
}).task({}, `{"selected_skill":"operator"}`);
callback(null, twiml);
};
Then from here an available operator will accept the task then dial an agent
The operator dials the client like this
{"skills":["operator"],"contact_uri":"client:joey"}
Please help
Thanks in advance
Based on my take on the associated documentation, it looks like the parameters are only sent to the Twilio client via TwiML, as shown here.
https://www.twilio.com/docs/voice/client/javascript/changelog#160-aug-29-2018
Added support for custom incoming parameters from TwiML as Map Connection.customParameters. When a TwiML application sends
custom parameters using the noun, these parameters will be
added to Connection.customParameters
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Client>
<Identity>alice</Identity>
<Parameter name="foo" value="bar"/>
<Parameter name="baz" value="123"/>
</Client>
</Dial>
</Response>
It sounds like you are using the Task Router JavaScript SDK, maybe you can add these parameters as attributes to the task, and then access those tasks from the Task Router client SDK?
How to call to a phone number using Twilio.Device?
I am doing the click to call feature for my widget.
I am able to get the Capability token required in setting up the Twilio.Device. I am able also to connect the Twilio.Device to twilio by setup function provided in client javascript library.
What can I use to call a number using Twilio.Device?
Its been a while since I played with it, but looking back at the code then once you have the token and are connected to Twilio it's just a case of setting the number you want to call and then initiating the call. Something like this:
document.getElementById('button-call').onclick = function () {
// get the phone number to connect the call to
var params = {
Caller: document.getElementById('phone-number').value
};
console.log('Calling ' + params.Caller + '...');
Twilio.Device.connect(params);
};
You have to make sure your token allows outgoing calls. My token.php file contains the following:
$capability = new ClientToken($TWILIO_ACCOUNT_SID, $TWILIO_AUTH_TOKEN, 'ttl=20');
$capability->allowClientOutgoing($TWILIO_TWIML_APP_SID);
$token = $capability->generateToken();
I think this is all fairly standard stuff from the quickstart files. I only had a quick go with it, but I don't remember it being complicated. Have fun!
I send calls out by building an html file that contains twiml markup, and use the php lib to place the call to the outgoing number (see e.g.)
$tw_call = $twilio_client->calls->create(
"+1".$recipient['address'], "+1".$org['twilio_number'],
array(
'Url' => VOICE_CALL_LINK.'/'.$file, (this contains the SAY verbs and text)
'Timeout' => '30',
'StatusCallback' => CALLBACK_LINK.'/voice_call_handler.php',
'StatusCallbackEvent' => array('initiated', 'ringing', 'answered', 'completed')
)
I want to know if it is possible to record a dtmf code from the call recipient via the method I am using for placing the call?
Can an additional callback url be placed in the text file? If so how would I capture which call the was coming back? Would the call sid be available to the possible callback url within the text file?
Ok I must be missing something. I tried the following:
<Response>
<Pause length='1'/>
<Say voice='alice'>$intro</Say>
<Pause length='1'/>
<Say voice='alice'>$msg_body</Say>
<Pause length='1'/>
<Gather action='absolute html path' numDigits='1'>
<Say Please respond by selecting 1 for I can come. Select 2 for I cannot come.</Say>
</Gather>
</Response>";
I get back from Twilio "an application error has occurred". If I remove the Gather tags and the Say tag within the Gather tags, I receive a perfect call.
Same error occurs if I leave the tags and remove the action and path.
Can you gather responses on outbound calls? I ask because all twilio docs refer to inbound call.
Twilio developer evangelist here.
In order to capture DTMF tones from a call you can use the <Gather> TwiML verb. This would probably go in the file which contains your <Say> that you point to in the code above. <Say> can be nested within <Gather> in order to allow you to ask the user for input and start taking it as soon as they start typing.
The TwiML might look like this:
<Response>
<Gather action="/gather_result.php" numDigits="1">
<Say>Welcome to the free beer hotline, dial 1 for free beer, dial 2 for other beverages.</Say>
</Gather>
</Response>
Then, when the user dials the number (you can control how many numbers with the numDigits attribute) Twilio will make a request to the URL in the action attribute. Within that request will be a Digits parameter which will contain the numbers the user pressed. The call SID would also be among the parameters.
Let me know if that helps at all.
I had similar issue where Gather TwiML was not capturing the user dtmf input from a call sent out of twilio. For some reasons, it failed to capture my input digit. I did press 1#, but the voice message keep playing and repeating the same message. Sometimes it works and twilio able to get the digit that I inputted, but more than 80% of the times I tried, it failed to capture the inputted digit. Below is the TwiML in node js looks like:
var promise = new Parse.Promise();
twilioClient.calls.create({
to: phoneNumber,
from:'+6598124124',
url: hosturl + '/gather_user_dial',
body: callParam,
statusCallback: hosturl + '/callback_user',
statusCallbackMethod: 'POST',
statusCallbackEvent: ["completed", "busy", "no-answer", "canceled", "failed"]
}).then(function(call) {
if (res) res.success(call);
promise.resolve(call);
}, function(error) {
console.error('Call failed! Reason: ' + error.message);
if (res) res.error(error);
promise.reject(error);
});
app.post('/gather_user_dial', (request, response) => {
const twiml = new VoiceResponse();
const gather = twiml.gather({
numDigits: 1,
timeout: 5,
actionOnEmptyResult: true,
action: '/gather',
});
gather.say('You are receiving a call from company A because you press the emergency button. Press 1 if you are okay or Press 9 if you need help, followed by the pound sign.');
twiml.redirect('/gather_user_dial');
response.type('text/xml');
response.send(twiml.toString());
});
app.post('/gather', (request, response) => {
const twiml = new VoiceResponse();
if (request.body.Digits) {
switch (request.body.Digits) {
case '1':
twiml.say('User has been notified!');
userPressOne(request.body.Called);
break;
case '9':
twiml.say('User has been notified!');
userPressNine(request.body.Called);
break;
default:
twiml.say("Sorry, I don't understand that choice.").pause();
twiml.redirect('/gather_user_dial');
break;
}
} else {
twiml.redirect('/gather_user_dial');
}
response.type('text/xml');
response.send(twiml.toString());
});
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);
};