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 an app that makes a outbound call to user A.
User A answers and either says "foo" or "bar"
If User A says "foo" -> c.calls(callSid).update(status="complete")
If User A says "bar" -> twilio.update('press the number 2')
How can I implement this?
I'm using the Python helper library and have tried this.
call = c.calls(callSid).update(twiml="<Play digits='2'/>")
But the app errors out.
In your outbound call use TwiML to ask the user to say "foo" or "bar" and then supply a webhook which can be called to process the response, i.e.:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Gather input="speech" timeout="5" action="<your webhook>">
<Say>Please say foo or bar.</Say>
</Gather>
</Response>
Twilio will do a POST to the specified webhook in action and the user answer will be stored in SpeechResult along with a confidence score in Confidence. You can then continue with your business logic as you outlined.
For more information on Gather have a look at the TwiML documentation.
I have an API which first creates a call to a number using the C# wrapper, lets say the receiver is +1000000001
var call = CallResource.Create(new PhoneNumber("+1000000001"),
new PhoneNumber("MYVERIFIEDNUMBER"),
url: new Uri("https://api.com/answered"),
method: HttpMethod.Get,
client: _client,
sendDigits: ""
);
When answered the TWIML returned from https://api.com/answered is
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Gather action="https://api.com/connect/6AE3045C0D024F1896BF7ECFCB2FC40A" method="GET">
<Say voice="alice" loop="0" language="en">Press any key to connect to John Doe, , </Say>
</Gather>
</Response>
This should result in an infinite loop in the voice of "alice" for the SAY verb being repeated to the receiver at +1000000001 but it is a male robotic voice and it only repeats once then drops the call. This is first part of the issue.
The second part is the GATHER verb does nothing. I should be able to press a touch tone phone and have the url https://api.com/connect/6AE3045C0D024F1896BF7ECFCB2FC40A return
<?xml version="1.0" encoding="utf-8"?>
<Response>
<Dial>client:6AE3045C0D024F1896BF7ECFCB2FC40A</Dial>
<Hangup></Hangup>
</Response>
which it does on the GET request but I can never get to it because of the GATHER issue
The third part is does this look correct to dial a client app?
<Dial>client:6AE3045C0D024F1896BF7ECFCB2FC40A</Dial>
Thanks for any advice
Looks like Alice defaults to en-US so you can leave the language attribute off. Also, can you make sure you are returning TwiML with the right MIME type, https://www.twilio.com/docs/voice/twiml#twilio-understands-mime-types.
Client is used inorrectly, refer to the TwiML syntax here, https://www.twilio.com/docs/voice/client/twiml.
Let me know if that addresses the issue.
I'm doing an application in Java that connects a phone call to Watson Voice Agent to my user via Twilio and i need to pass some information to the Voice Agent and make it available to the assistant.
I'm passing the information on the sip invite header but I can't get the information on the assistant dialog.
My Twilio call class:
public String callPhone(String to, String from,String data)throws URISyntaxException{
Twilio.init(ACCOUNT_SID, AUTH_TOKEN);
Call call = Call.creator(
new com.twilio.type.PhoneNumber(to),
new com.twilio.type.PhoneNumber(from),
new URI("https://handler.twilio.com/twiml/xxxx?data_sent="+data))
.create();
return call.getSid();
}
My TwinML Bin code:
<?xml version="1.0" encoding="UTF-8"?>
<Response>
<Dial>
<Sip>sip:{{From}}#us-south.voiceagent.cloud.ibm.com?X-data={{data_sent}}</Sip>
</Dial\>
</Response>
In my Voice Agent config I put the "Custom SIP INVITE header" as "data" (without quotes) and in the Assistant I try to access $vgwSIPCustomInviteHeader but the Voice Agent doesn't say anything where this value should be.
I've already solved it, for some reason if I use the header parameter with "_" the TwinML Bin doesn't seem to be able to send the value correctly, I changed the parameter to "dataSent" and now it works fine.
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);
};