Click To Call with Java and Spring

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 Java and Spring 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.

Ready to go?  Click the button below to begin.

Setup the 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. We'll store 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.

Loading Code Samples...
Language
# Twilio API credentials
# Found at https://www.twilio.com/user/account/voice
# export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# export TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Twilio phone number
# Purchase one at https://www.twilio.com/user/account/phone-numbers
# export TWILIO_NUMBER=+15552737123
.env.example
Environment Configuration file

.env.example

Next up, let's take a look at creating the user facing web form.

The Web Form

For our solution, we're going to need a nice user facing web form on our website.

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

Loading Code Samples...
Language
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Click To Call Tutorial</title>
    <link rel="icon" type="image/png" href="/img/favicon.ico" />
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"
          type="text/css" />
    <link rel="stylesheet"
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"
          type="text/css" />
    <link rel="stylesheet" media="screen" href="/intl-phone/css/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 />
    <div class="row">
        <div class="col-md-12">
            <form id="contactform" role="form" action="#" method="POST">
                <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>
                <label>Your number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="userPhone" id="userPhone"
                           placeholder="(651) 555-7889"/>
                </div>
                <label>Sales team number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="salesPhone" id="salesPhone"
                           placeholder="(651) 555-7889"/>
                </div>
                <button type="submit" class="btn btn-default">Contact Sales</button>
            </form>

        </div>
    </div>
</div>
<!-- /page -->
</body>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="intl-phone/js/intlTelInput.js" data-turbolinks-track="true"></script>
<script src="intl-phone/libphonenumber/build/utils.js" data-turbolinks-track="true"></script>
<script src="js/clicktocall.js" data-turbolinks-track="true"></script>
</html>
src/main/resources/templates/index.html
User facing web form

src/main/resources/templates/index.html

So what does this form need?

  • An input for a phone number
  • A submit button.

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 take a look at that code, 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:

  • Watch for the user "submitting" the form element
  • 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() {
    $('#userPhone, #salesPhone').intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $('#contactform'),
        $submit = $('#contactform input[type=submit]');

    // Intercept form submission
    $form.on('submit', function(e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr('disabled', 'disabled');

        // Submit the form via ajax
        $.ajax({
            url:'/call',
            method:'POST',
            data: $form.serialize()
        }).done(function(data) {
            content = JSON.parse(data);
            alert(content.message);
        }).fail(function() {
            alert('There was a problem calling you - please try again later.');
        }).always(function() {
            $submit.removeAttr('disabled');
        });

    });
});
src/main/resources/static/js/clicktocall.js
Submit the form with AJAX

src/main/resources/static/js/clicktocall.js

Now that we have the front end done let's build the back end that will receive this data.  We'll start our exploration in the next step.

Instantiate a Client Object

First we instantiate a twilioRestClient object with our Account SID and Auth Token. This is essentially our Java REST API handler, which we could use to send SMSes (or a myriad of other things).

In this example, we're defining it as a Spring Bean. This way we can use the framework Dependency Injection capabilities later.

Loading Code Samples...
Language
package com.twilio.clicktocall;

import com.twilio.clicktocall.twilio.TwilioLine;
import com.twilio.clicktocall.twilio.TwilioRequestValidator;
import com.twilio.http.TwilioRestClient;
import com.twilio.security.RequestValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@ComponentScan
@Configuration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public TwilioRestClient twilioRestClient(@Value("${TWILIO_ACCOUNT_SID}") String accountSid,
                                      @Value("${TWILIO_AUTH_TOKEN}") String authToken){
        return new TwilioRestClient.Builder(accountSid, authToken).build();
    }

    @Bean
    public TwilioRequestValidator twilioRequestValidator(@Value("${TWILIO_AUTH_TOKEN}") String authToken) {
        return new TwilioRequestValidator(new RequestValidator(authToken));
    }

    @Bean
    public TwilioLine twilioLine(TwilioRestClient restClient, @Value("${TWILIO_NUMBER}") String twilioNumber) {
        return new TwilioLine(restClient, twilioNumber);
    }
}
src/main/java/com/twilio/clicktocall/Application.java
Instantiate a twilioRestClient object

src/main/java/com/twilio/clicktocall/Application.java

Next we'll look at making a phone call.

Make a Phone Call

Next we'll use the CallCreator object to make an outgoing phone call which requires us to pass 3 parameters: a To number, a From number and the 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 future steps.

Loading Code Samples...
Language
package com.twilio.clicktocall.twilio;

import com.twilio.clicktocall.CallException;
import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.CallCreator;
import com.twilio.type.PhoneNumber;

import java.net.URI;
import java.net.URISyntaxException;

public class TwilioLine {
    private String twilioNumber;
    private TwilioRestClient restClient;

    public TwilioLine(TwilioRestClient restClient, String twilioNumber) {
        this.restClient = restClient;
        this.twilioNumber = twilioNumber;
    }

    public void call(final String phoneNumber, final String responseUrl)  {
        try {
            CallCreator callCreator = new CallCreator(new PhoneNumber(phoneNumber), new PhoneNumber(twilioNumber), new URI(responseUrl));
            callCreator.create(restClient);
        } catch (URISyntaxException e) {
            throw new CallException(e);
        }
    }
}
src/main/java/com/twilio/clicktocall/twilio/TwilioLine.java
Call Controller

src/main/java/com/twilio/clicktocall/twilio/TwilioLine.java

Next, let's look at the endpoint we'll expose to Twilio.

The Connect 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
package com.twilio.clicktocall;

import com.twilio.clicktocall.twilio.TwilioRequestValidator;
import com.twilio.clicktocall.twilio.TwiMLUtil;
import com.twilio.twiml.TwiMLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class ConnectController {

    private final static String SAY_MESSAGE = "Thanks for contacting our sales department. Our " +
            "next available representative will take your call.";

    private final TwilioRequestValidator requestValidator;

    @Autowired
    public ConnectController(TwilioRequestValidator requestValidator) {
        this.requestValidator = requestValidator;
    }

    @RequestMapping(value = "connect/{salesPhone}", produces = "application/xml")
    public ResponseEntity<String> connect(@PathVariable String salesPhone, HttpServletRequest request) throws TwiMLException {
        final HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_XML);

        if (requestValidator.validate(request)) {
            return new ResponseEntity<>(
              TwiMLUtil.buildVoiceResponseAndDial(SAY_MESSAGE, salesPhone),
              httpHeaders,
              HttpStatus.OK
            );
        } else {
            return new ResponseEntity<>("Invalid twilio request", HttpStatus.BAD_REQUEST);
        }
    }
}
src/main/java/com/twilio/clicktocall/ConnectController.java
Call Connect Controller

src/main/java/com/twilio/clicktocall/ConnectController.java

A publicly exposed URL has some drawbacks.  Next we'll look at making sure we don't leak any sensitive data.

Validate the Twilio Request

Often, our TwiML will include sensitive details such as user or staff phoine numbers.  Before sending TwiML back on a request, we should validate that we're actually talking to Twilio. 

Twilio Request Validator

The validate method provides a mechanism to confirm that the request your application is receiving is actually coming from us.  We use an instance of RequestValidator and call the validate method using the required parameters.

Loading Code Samples...
Language
package com.twilio.clicktocall.twilio;

import com.twilio.security.RequestValidator;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class TwilioRequestValidator {

    private final RequestValidator requestValidator;

    public TwilioRequestValidator(RequestValidator requestValidator) {
        this.requestValidator = requestValidator;
    }

    public boolean validate(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        Map<String, String> params = extractParametersFrom(request);

        String signature = request.getHeader("X-Twilio-Signature");

        return requestValidator.validate(url, params, signature);
    }

    private Map<String, String> extractParametersFrom(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();

        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String currentName = names.nextElement();
            params.put(currentName, request.getParameter(currentName));
        }
        return params;
    }
}
src/main/java/com/twilio/clicktocall/twilio/TwilioRequestValidator.java
Twilio Request Validator

src/main/java/com/twilio/clicktocall/twilio/TwilioRequestValidator.java

So now that you know you're talking to Twilio, let's move on and look at the TwiML response we'll be sending.

Generate Some TwiML

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

In this case our instructions inform Twilio to SAY something to the user and then DIAL the support agent's number so the customer can talk to him or her.

In order to make writing TwiML easy, the helper libraries have methods that generate TwiML for you. We use twilio-java to create a TwiML response that will instruct Twilio to SAY something.

Loading Code Samples...
Language
package com.twilio.clicktocall.twilio;

import com.twilio.twiml.Dial;
import com.twilio.twiml.Number;
import com.twilio.twiml.Say;
import com.twilio.twiml.TwiMLException;
import com.twilio.twiml.VoiceResponse;

public class TwiMLUtil {

    public static String buildVoiceResponseAndDial(String say, String salesPhone) throws TwiMLException {
        Number number = new Number.Builder(salesPhone).build();
        return new VoiceResponse.Builder()
                .say(new Say.Builder(say).build())
                .dial(new Dial.Builder().number(number).build())
                .build()
                .toXml();
    }
}
src/main/java/com/twilio/clicktocall/twilio/TwiMLUtil.java
Generate a TwiML response

src/main/java/com/twilio/clicktocall/twilio/TwiMLUtil.java

And with that we've implemented click to call on our website!  You should now be able to easily integrate it for your own use case.

On the next pane, we'll explore some other features you might like to add to your application.

Where to Next?

We love Java here at Twilio!  Let us prove it; here are a couple other excellent tutorials:

Automated Survey

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

SMS and MMS Notifications

Send SMS alerts to a list of system administrators when something goes wrong on your server.

Did this help?

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

Kevin Whinnery
Paul Kamp
Andrew Baker
Agustin Camino

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...
# Twilio API credentials
# Found at https://www.twilio.com/user/account/voice
# export TWILIO_ACCOUNT_SID=ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# export TWILIO_AUTH_TOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

# Twilio phone number
# Purchase one at https://www.twilio.com/user/account/phone-numbers
# export TWILIO_NUMBER=+15552737123
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Click To Call Tutorial</title>
    <link rel="icon" type="image/png" href="/img/favicon.ico" />
    <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css"
          type="text/css" />
    <link rel="stylesheet"
          href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap-theme.min.css"
          type="text/css" />
    <link rel="stylesheet" media="screen" href="/intl-phone/css/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 />
    <div class="row">
        <div class="col-md-12">
            <form id="contactform" role="form" action="#" method="POST">
                <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>
                <label>Your number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="userPhone" id="userPhone"
                           placeholder="(651) 555-7889"/>
                </div>
                <label>Sales team number</label>
                <div class="form-group">
                    <input class="form-control" type="text" name="salesPhone" id="salesPhone"
                           placeholder="(651) 555-7889"/>
                </div>
                <button type="submit" class="btn btn-default">Contact Sales</button>
            </form>

        </div>
    </div>
</div>
<!-- /page -->
</body>
<script src="//code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="intl-phone/js/intlTelInput.js" data-turbolinks-track="true"></script>
<script src="intl-phone/libphonenumber/build/utils.js" data-turbolinks-track="true"></script>
<script src="js/clicktocall.js" data-turbolinks-track="true"></script>
</html>
// Execute JavaScript on page load
$(function() {
    $('#userPhone, #salesPhone').intlTelInput({
        responsiveDropdown: true,
        autoFormat: true
    });
    var $form = $('#contactform'),
        $submit = $('#contactform input[type=submit]');

    // Intercept form submission
    $form.on('submit', function(e) {
        // Prevent form submission and repeat clicks
        e.preventDefault();
        $submit.attr('disabled', 'disabled');

        // Submit the form via ajax
        $.ajax({
            url:'/call',
            method:'POST',
            data: $form.serialize()
        }).done(function(data) {
            content = JSON.parse(data);
            alert(content.message);
        }).fail(function() {
            alert('There was a problem calling you - please try again later.');
        }).always(function() {
            $submit.removeAttr('disabled');
        });

    });
});
package com.twilio.clicktocall;

import com.twilio.clicktocall.twilio.TwilioLine;
import com.twilio.clicktocall.twilio.TwilioRequestValidator;
import com.twilio.http.TwilioRestClient;
import com.twilio.security.RequestValidator;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

@EnableAutoConfiguration
@ComponentScan
@Configuration
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Bean
    public TwilioRestClient twilioRestClient(@Value("${TWILIO_ACCOUNT_SID}") String accountSid,
                                      @Value("${TWILIO_AUTH_TOKEN}") String authToken){
        return new TwilioRestClient.Builder(accountSid, authToken).build();
    }

    @Bean
    public TwilioRequestValidator twilioRequestValidator(@Value("${TWILIO_AUTH_TOKEN}") String authToken) {
        return new TwilioRequestValidator(new RequestValidator(authToken));
    }

    @Bean
    public TwilioLine twilioLine(TwilioRestClient restClient, @Value("${TWILIO_NUMBER}") String twilioNumber) {
        return new TwilioLine(restClient, twilioNumber);
    }
}
package com.twilio.clicktocall.twilio;

import com.twilio.clicktocall.CallException;
import com.twilio.http.TwilioRestClient;
import com.twilio.rest.api.v2010.account.CallCreator;
import com.twilio.type.PhoneNumber;

import java.net.URI;
import java.net.URISyntaxException;

public class TwilioLine {
    private String twilioNumber;
    private TwilioRestClient restClient;

    public TwilioLine(TwilioRestClient restClient, String twilioNumber) {
        this.restClient = restClient;
        this.twilioNumber = twilioNumber;
    }

    public void call(final String phoneNumber, final String responseUrl)  {
        try {
            CallCreator callCreator = new CallCreator(new PhoneNumber(phoneNumber), new PhoneNumber(twilioNumber), new URI(responseUrl));
            callCreator.create(restClient);
        } catch (URISyntaxException e) {
            throw new CallException(e);
        }
    }
}
package com.twilio.clicktocall;

import com.twilio.clicktocall.twilio.TwilioRequestValidator;
import com.twilio.clicktocall.twilio.TwiMLUtil;
import com.twilio.twiml.TwiMLException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.servlet.http.HttpServletRequest;

@Controller
public class ConnectController {

    private final static String SAY_MESSAGE = "Thanks for contacting our sales department. Our " +
            "next available representative will take your call.";

    private final TwilioRequestValidator requestValidator;

    @Autowired
    public ConnectController(TwilioRequestValidator requestValidator) {
        this.requestValidator = requestValidator;
    }

    @RequestMapping(value = "connect/{salesPhone}", produces = "application/xml")
    public ResponseEntity<String> connect(@PathVariable String salesPhone, HttpServletRequest request) throws TwiMLException {
        final HttpHeaders httpHeaders = new HttpHeaders();
        httpHeaders.setContentType(MediaType.APPLICATION_XML);

        if (requestValidator.validate(request)) {
            return new ResponseEntity<>(
              TwiMLUtil.buildVoiceResponseAndDial(SAY_MESSAGE, salesPhone),
              httpHeaders,
              HttpStatus.OK
            );
        } else {
            return new ResponseEntity<>("Invalid twilio request", HttpStatus.BAD_REQUEST);
        }
    }
}
package com.twilio.clicktocall.twilio;

import com.twilio.security.RequestValidator;

import javax.servlet.http.HttpServletRequest;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

public class TwilioRequestValidator {

    private final RequestValidator requestValidator;

    public TwilioRequestValidator(RequestValidator requestValidator) {
        this.requestValidator = requestValidator;
    }

    public boolean validate(HttpServletRequest request) {
        String url = request.getRequestURL().toString();
        Map<String, String> params = extractParametersFrom(request);

        String signature = request.getHeader("X-Twilio-Signature");

        return requestValidator.validate(url, params, signature);
    }

    private Map<String, String> extractParametersFrom(HttpServletRequest request) {
        Map<String, String> params = new HashMap<>();

        Enumeration<String> names = request.getParameterNames();
        while (names.hasMoreElements()) {
            String currentName = names.nextElement();
            params.put(currentName, request.getParameter(currentName));
        }
        return params;
    }
}
package com.twilio.clicktocall.twilio;

import com.twilio.twiml.Dial;
import com.twilio.twiml.Number;
import com.twilio.twiml.Say;
import com.twilio.twiml.TwiMLException;
import com.twilio.twiml.VoiceResponse;

public class TwiMLUtil {

    public static String buildVoiceResponseAndDial(String say, String salesPhone) throws TwiMLException {
        Number number = new Number.Builder(salesPhone).build();
        return new VoiceResponse.Builder()
                .say(new Say.Builder(say).build())
                .dial(new Dial.Builder().number(number).build())
                .build()
                .toXml();
    }
}