Click To Call with Python and Flask

Download the Code

Wish your users could get in touch as easily as they can surf? It's your lucky day!

Let's go over the steps necessary to implement click-to-call in a Python and Flask application.

Click to Call

 

  1. A website visitor submits a web form with a phone number.
  2. Your web application receives the submission and initiates an HTTP request to Twilio asking to initiate an outbound call.
  3. Twilio receives the request and initiates a call to the user's phone number.
  4. The user picks up the call.
  5. After the call connects, we provide TwiML instructions to connect the user to our sales or support teams.

*Check out how iAdvize uses Twilio Click-to-call to connect online shoppers with customer support representatives.*

What We Will Learn

This tutorial demonstrates how to initialize a call using the Twilio REST API and how to create a call using the TwiML Say verb.

Let's get started! Click the button below to move to the next step of the tutorial.

Setup Our Environment

To create our click-to-call application we need to setup our environment first.

Let's put our Twilio credentials in a place where our application can access them. For the purposes of this tutorial, we'll place them as environment variables that our application can read.

You can find your Twilio credentials in the console.

Retrieve Your Twilio Credentials

For more instructions on how to run the application, refer to the app's README file.

Make a Nice Web Form

The first step in creating a real click-to-call solution is to build the form that your user needs to fill out on your site.

No need to overthink this; the real goal is to POST the User's phone number to your controller.

Loading Code Samples...
Language
<!DOCTYPE html>
<html>
  <head>
      <title>Twilio Click to Call for Heroku and Flask</title>
      <!-- We use Twitter Bootstrap as the default styling for our page-->
      <link rel="stylesheet" 
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
      <link rel="stylesheet" 
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"> 
        <!-- Include CSS for our third-party telephone input jQuery plugin-->
      <link rel="stylesheet" href="/static/styles/intlTelInput.css">
  </head>
  <body>
    <div class="container">
        <h1>Click To Call</h1>
        <p>
            Click To Call converts your website's users into engaged customers by 
            creating an easy way for your customers to contact your sales and 
            support teams right on your website.
        </p>
        <p>Here's an example of how it's done!</p>
        <hr>

        <!-- C2C contact form-->
        <div class="row">
            <div class="col-md-12">
                <form id="contactForm" role="form">
                    <div class="form-group">
                        <h3>Call Sales</h3>
                        <p class="help-block">
                            Are you interested in impressing your friends and 
                            confounding your enemies? Enter your phone number 
                            below, and our team will contact you right away.
                        </p>
                    </div>
                    <div class="form-group">
                        <input type="text" id="phoneNumber" 
                            placeholder="(651) 555-7889" class="form-control">
                    </div>
                    <button type="submit" class="btn btn-default">
                        Contact Sales
                    </button>
                </form>
            </div>
        </div>
    </div>
    <!-- Include page dependencies -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src="/static/js/intlTelInput.min.js"></script>
    <script src="/static/js/app.js"></script>
  </body>
</html>
clicktocall/templates/index.html
A user facing web form

clicktocall/templates/index.html

Since the page doesn't need to render new content after clicking on submit, we decided to implement the POST action via Ajax using jQuery. Let's look at that next.

Submit the Form

To make the click to call feature more seamless we used Ajax to send the form asynchronously. This code shows one way you could implement this functionality using jQuery.

This code does the following:

  • Watch for the user submitting the form
  • Submit the form data to our controller
  • Let the user know if the submission was successful or not

This is a common implementation of jQuery's $.ajax() method. Notice that we are returning the response message when the call has connected.

Loading Code Samples...
Language
// Execute JavaScript on page load
$(function() {
    // Initialize phone number text input plugin
    $('#phoneNumber').intlTelInput({
        responsiveDropdown: true,
        autoFormat: true,
        utilsScript: '/static/js/libphonenumber/src/utils.js'
    });

    // Intercept form submission and submit the form with ajax
    $('#contactForm').on('submit', function(e) {
        // Prevent submit event from bubbling and automatically
        // submitting the form
        e.preventDefault();

        // Call our ajax endpoint on the server to initialize the
        // phone call
        $.ajax({
            url: '/call',
            method: 'POST',
            dataType: 'json',
            data: {
                phoneNumber: $('#phoneNumber').val()
            }
        }).done(function(data) {
            // The JSON sent back from the server will contain
            // a success message
            alert(data.message);
        }).fail(function(error) {
            alert(JSON.stringify(error));
        });
    });
});
clicktocall/static/js/app.js
'Submit' the web form with jQuery

clicktocall/static/js/app.js

Now that we have the front end done let's build the backend.  We'll explore in the next step.

Instantiate the Client Object

First, we instantiate a TwilioRestClient object with our Account SID and Auth Token.

This is essentially our Python REST API handler, which we could use to send SMSes (or a myriad of other things). But for now, we just need it to get access to the object that we're going to use to create phone calls.

Loading Code Samples...
Language
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
clicktocall/app.py
Application Core Controller

clicktocall/app.py

Let's look at how to make a phone call.

Make a Phone Call With Twilio

Next we'll use the create method from the calls object to make an outgoing phone call.  This requires us to pass a From number, a To number and a URL parameter that tells Twilio what to do after it connects the call to our user.

In this case Twilio needs to dial in the Agent once the call has been placed. We'll discuss this more in the next step.

Loading Code Samples...
Language
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
clicktocall/app.py
Make an outbound phone call

clicktocall/app.py

Let's take a look at how to define the outbound endpoint.

The Outbound Endpoint

Twilio makes a request to our application when the call is created using the REST API.

This means that we need to create an endpoint that is publicly available for internet requests.

Loading Code Samples...
Language
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
clicktocall/app.py
Application Core Controller

clicktocall/app.py

Next we will see how to generate the TwiML required for Twilio to take actions on behalf of our application.

Generating TwiML

TwiML is a set of simple verbs and nouns written in XML that Twilio reads as instructions.

In this case our instructions inform Twilio to simply SAY something to the user. If we wanted to connect the user to a real support agent, we would have to add a Dial verb that specifies the support agent number so the customer can talk to him or her.

In order to make writing TwiML easy, many of the helper libraries have methods that generate TwiML for you. In this case we use the twilio Python library to create a TwiML response that will instruct Twilio to say something.

Loading Code Samples...
Language
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
clicktocall/app.py
Connect an 'agent' to the user

clicktocall/app.py

And that's it!

We've just implemented click to call on our web page that will allow customers to connect with a support agent.  You're ready to add it to your application!

Where to Next?

We love Python here at Twilio.  Here are a couple other Python tutorials:

Automated Survey

Instantly collect structured data from your users with a survey conducted over a voice call or SMS text messages.

Appointment Reminders

Cut down on the no-shows with automatic customer appointment reminders sent out in the background.

Did this help?

Thanks for checking this tutorial out! Tweet to us @twilio and let us know what you're building!

Agustin Camino
David Prothero
Kevin Whinnery
Paul Kamp
Andrew Baker

Need some help?

We all do sometimes; code is hard. Get help now from our support team, or lean on the wisdom of the crowd browsing the Twilio tag on Stack Overflow.

1 / 1
Loading Code Samples...
<!DOCTYPE html>
<html>
  <head>
      <title>Twilio Click to Call for Heroku and Flask</title>
      <!-- We use Twitter Bootstrap as the default styling for our page-->
      <link rel="stylesheet" 
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
      <link rel="stylesheet" 
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"> 
        <!-- Include CSS for our third-party telephone input jQuery plugin-->
      <link rel="stylesheet" href="/static/styles/intlTelInput.css">
  </head>
  <body>
    <div class="container">
        <h1>Click To Call</h1>
        <p>
            Click To Call converts your website's users into engaged customers by 
            creating an easy way for your customers to contact your sales and 
            support teams right on your website.
        </p>
        <p>Here's an example of how it's done!</p>
        <hr>

        <!-- C2C contact form-->
        <div class="row">
            <div class="col-md-12">
                <form id="contactForm" role="form">
                    <div class="form-group">
                        <h3>Call Sales</h3>
                        <p class="help-block">
                            Are you interested in impressing your friends and 
                            confounding your enemies? Enter your phone number 
                            below, and our team will contact you right away.
                        </p>
                    </div>
                    <div class="form-group">
                        <input type="text" id="phoneNumber" 
                            placeholder="(651) 555-7889" class="form-control">
                    </div>
                    <button type="submit" class="btn btn-default">
                        Contact Sales
                    </button>
                </form>
            </div>
        </div>
    </div>
    <!-- Include page dependencies -->
    <script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
    <script src="/static/js/intlTelInput.min.js"></script>
    <script src="/static/js/app.js"></script>
  </body>
</html>
// Execute JavaScript on page load
$(function() {
    // Initialize phone number text input plugin
    $('#phoneNumber').intlTelInput({
        responsiveDropdown: true,
        autoFormat: true,
        utilsScript: '/static/js/libphonenumber/src/utils.js'
    });

    // Intercept form submission and submit the form with ajax
    $('#contactForm').on('submit', function(e) {
        // Prevent submit event from bubbling and automatically
        // submitting the form
        e.preventDefault();

        // Call our ajax endpoint on the server to initialize the
        // phone call
        $.ajax({
            url: '/call',
            method: 'POST',
            dataType: 'json',
            data: {
                phoneNumber: $('#phoneNumber').val()
            }
        }).done(function(data) {
            // The JSON sent back from the server will contain
            // a success message
            alert(data.message);
        }).fail(function(error) {
            alert(JSON.stringify(error));
        });
    });
});
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)
from flask import Flask
from flask import jsonify
from flask import render_template
from flask import request
from flask import url_for

from twilio.twiml.voice_response import VoiceResponse
from twilio.rest import Client

# Declare and configure application
app = Flask(__name__, static_url_path='/static')
app.config.from_pyfile('local_settings.py')


# Route for Click to Call demo page.
@app.route('/')
def index():
    return render_template('index.html',
                           configuration_error=None)


# Voice Request URL
@app.route('/call', methods=['POST'])
def call():
    # Get phone number we need to call
    phone_number = request.form.get('phoneNumber', None)

    try:
        twilio_client = Client(app.config['TWILIO_ACCOUNT_SID'],
                               app.config['TWILIO_AUTH_TOKEN'])
    except Exception as e:
        msg = 'Missing configuration variable: {0}'.format(e)
        return jsonify({'error': msg})

    try:
        twilio_client.calls.create(from_=app.config['TWILIO_CALLER_ID'],
                                   to=phone_number,
                                   url=url_for('.outbound',
                                               _external=True))
    except Exception as e:
        app.logger.error(e)
        return jsonify({'error': str(e)})

    return jsonify({'message': 'Call incoming!'})


@app.route('/outbound', methods=['POST'])
def outbound():
    response = VoiceResponse()

    response.say("Thank you for contacting our sales department. If this "
                 "click to call application was in production, we would "
                 "dial out to your sales team with the Dial verb.",
                 voice='alice')
    '''
    # Uncomment this code and replace the number with the number you want
    # your customers to call.
    response.number("+16518675309")
    '''
    return str(response)


# Route for Landing Page after Heroku deploy.
@app.route('/landing.html')
def landing():
    return render_template('landing.html',
                           configuration_error=None)