Conversational AI • Last Updated 6/22/2024

Build Audio Conferencing Into Your App with Ruby

Read the step-by-step guide to embedding feature-rich audio conferencing into your application using our Ruby SDK.

By Pete Christianson

Ruby audio conferencing banner

How does Call Control conferencing work?

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.

Getting Started with Call Control Conferencing

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:

  • Set the Connection Type to Call Control
  • Make sure the Implementation is Webhook, and the Webhook API Version is API v2
  • Fill in the Webhook URL with the address the server will be running on. Alternatively, you can use a service like Ngrok to temporarily forward a local port to the internet to a random address and use that. We'll talk about this in more detail later.

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]

Creating a conference with Ruby

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']}

Authentication

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.

Using conferencing

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.

Complete running Call Control conference application

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.

Share on Social

Related articles

Sign up and start building.