Read the step-by-step guide to embedding feature-rich audio conferencing into your application using our Ruby SDK.
By Pete Christianson
The Call Control framework is a set of REST APIs that allow you to control the complete call flow from the moment a call comes in (or out) to the moment you terminate that call. In between, you’ll receive a number of webhooks for each step of the call, which you answer with a command of your choice. It's this back and forward communication that gives you such granular control over your call. A subset of operations available in the Voice API is the Call Conferencing API. This allows you to create and manage a conference programmatically when making or receiving a call.
The Telnyx Ruby Library is a convenient wrapper around the Telnyx REST API. It allows you to access and control call flows using an intuitive object-oriented library. In this tutorial, we'll walk you through creating a simple Sinatra server that allows you to create and manage a conference.
Before we begin, make sure you have the Telnyx and Sinatra gems installed.
gem install telnyx sinatra
Alternatively, create a Gemfile for your project:
source 'https://rubygems.org'
gem 'sinatra'
gem 'telnyx'
Follow the quickstart guide to set up a Mission Control Portal account, buy a number and assign that number to a Connection.
The connection needs to be set up to work with the call conferencing API:
Finally, you need to create an API token - make sure you save the token somewhere safe. Now create a file such as conference_demo_server.rb, then write the following to setup the telnyx library.
require 'sinatra'
require 'telnyx'
CONFIG = {
# The following 3 keys need to be filled out
telnyx_api_key: 'YOUR_API_KEY',
phone_number: 'TELNYX_PHONE_NUMBER', # the number that will be used for accessing the conference
connection_id: 'CONNECTION_ID', # the connection id for phone number above
}
# Setup telnyx api key.
Telnyx.api_key = CONFIG[:telnyx_api_key]
Now that you have setup your auth token, phone number, and connection, you can begin to use the API Library to make and manage conferences. First, you'll need to setup a Sinatra endpoint to receive webhooks for call and conference events.
There are a number of webhooks that you should anticipate receiving during the lifecycle of each call and conference. This will allow you to take action in response to any number of events triggered during a call. In this example, you will use the call.initiated and call.answered events to add call to a conference. You'll need to wait until there is a running call before you can create a conference, so plan to use call events to create the conference after a call is initiated.
# ...
# Declare script level variables
calls = []
conference = nil
set :port, 9090
post "/webhook" do
# Parse the request body.
request.body.rewind
data = JSON.parse(request.body.read)['data']
# Handle events
if data['record_type'] == 'event'
case data['event_type']
when 'call.initiated'
# Create a new call object.
call = Telnyx::Call.new id: data['payload']['call_control_id'],
call_leg_id: data['payload']['call_leg_id']
# Save the new call object into our call list for later use.
calls << call
# Answer the call, this will cause the api to send another webhook event
# of the type call.answered, which we will handle below.
call.answer
when 'call.answered'
# Find the stored call, which was created during a call.initiated event.
call = calls.find { |call| call.id == data['payload']['call_control_id'] }
# Create a new conference if this is the first caller and there
# is no conference running yet.
if conference.nil?
conference = Telnyx::Conferences.create call_control_id: call.id,
name: 'demo-conference'
# If there is a conference, then add the new caller.
else
conference.join call_control_id: call.id
end
when 'call.hangup'
# Remove the ended call from the active call list
calls.reject! {|call| call.call_leg_id == data['payload']['call_leg_id']}
end
end
end
Pat youself on the back - that's a lot of code to go through! Now let's break it down even further and explain what it does. First, create an array for keeping track of the ongoing calls and define a variable for storing the conference object. Then, tell Sinatra to listen on port 9090 and create an endpoint at /webhook, which can be anything you choose; here we just call it webhook.
calls = []
conference = nil
set :port, 9090
post "/webhook" do
# ...
end
Next, parse the data from the API server, check to see if it is a webhook event, and act on it if it is. Then, you'll define what actions to take on different types of events.
post "/webhook" do
request.body.rewind
data = JSON.parse(request.body.read)['data']
if data['record_type'] == 'event'
case data['event_type']
# ...
end
end
This is where you'll respond to a new call being initiated, which can be from either an inbound or outbound call. Create a new Telnyx::Call object and store it in the active call list, then call call.answer to answer it if it's an inbound call.
when 'call.initiated'
call = Telnyx::Call.new id: data['payload']['call_control_id'],
call_leg_id: data['payload']['call_leg_id']
calls << call
call.answer
On the call.answered event, retrieve the stored call created during the call.initiated event. Then, either create a new conference if this is the first call and there isn't a conference running yet, or add the call to an existing conference. Note that a call control id is required to start a conference, so there must aready be an existing call before you can create a conference, which is why we create the conference here.
when 'call.answered'
call = calls.find { |call| call.id == data['payload']['call_control_id'] }
if conference.nil?
conference = Telnyx::Conferences.create call_control_id: call.id,
name: 'demo-conference'
else
conference.join call_control_id: call.id
end
And finally, when a call ends, we remove it from the active call list.
when 'call.hangup'
puts 'Call hung up'
calls.reject! {|call| call.call_leg_id == data['payload']['call_leg_id']}
Now you have a working conference application! But, how secure is it? Could a third party simply craft fake webhooks to manipulate the call flow logic of your application? Telnyx has you covered with a powerful signature verification system! Simply make the following changes:
# ...
ENV['TELNYX_PUBLIC_KEY'] = 'YOUR_PUBLIC_KEY' # Please fetch the public key from: https://portal.telnyx.com/#/app/account/public-key
post '/webhook' do
request.body.rewind
body = request.body.read # Save the body for verification later
data = JSON.parse(body)['data']
Telnyx::Webhook::Signature.verify(body,
request.env['HTTP_TELNYX_SIGNATURE_ED25519'],
request.env['HTTP_TELNYX_TIMESTAMP'])
# ...
Fill in the public key from the Telnyx Portal here. Telnyx::Webhook::Signature.verify will do the work of verifying the authenticity of the message, and raise SignatureVerificationError if the signature does not match the payload.
If you used a Gemfile, start the conference server with bundle exec ruby conference_demo_server.rb, if you're using globally installed gems use ruby conference_demo_server.rb.
When you're able to run the server locally, the final step involves making your application accessible from the internet. So far, we've set up a local web server. This is typically not accessible from the public internet, making testing inbound requests to web applications difficult.
The best workaround is a tunneling service. They come with client software that runs on your computer and opens an outgoing permanent connection to a publicly available server in a data center. Then, they assign a public URL (typically on a random or custom subdomain) on that server to your account. The public server acts as a proxy that accepts incoming connections to your URL, forwards (tunnels) them through the already established connection and sends them to the local web server as if they originated from the same machine.
The most popular tunneling tool is ngrok. Check out the ngrok setup walkthrough to set it up on your computer and start receiving webhooks from inbound messages to your newly created application.
Once you've set up ngrok or another tunneling service you can add the public proxy URL to your Connection in the MIssion Control Portal. To do this, click the edit symbol [✎] next to your Connection. In the "Webhook URL" field, paste the forwarding address from ngrok into the Webhook URL field. Add /webhooks to the end of the URL to direct the request to the webhook endpoint in your Sinatra server.
For now you'll leave “Failover URL” blank, but if you'd like to have Telnyx resend the webhook in the case where sending to the Webhook URL fails, you can specify an alternate address in this field.
The api-v2 directory contains an extended version of the tutorial code above, with the added ability to control the conference from the console! See the comments in the code for details on invoking the commands.
Questions? Get in touch with our technical experts via slack 24/7.
Related articles