Call your CRM Platform! Using an ASP.NET Web API to Link Twilio and Salesforce.com

I love mashups. It’s fun to combine technologies in unexpected ways. So when Wade Wegner of Salesforce asked me to participate in a webinar about the new Salesforce Toolkit for .NET, I decided to think of something unique to demonstrate. So, I showed off how to link Twilio – which in an API-driven service for telephony and SMS – with Salesforce.com data. In this scenario, job applicants can call a phone number, enter their tracking ID and hear the current status of their application. The rest of this blog post walks through what I built.

The Salesforce.com application

In my developer sandbox, I added a new custom object called Job Application that holds data about applicants, which job they applied to, and the status of the application (e.g. Submitted, In Review, Rejected).

2014.04.17forcetwilio01

I then created a bunch of records for job applicants. Here’s an example of one applicant in my system.

2014.04.17forcetwilio02

I want to expose a programmatic interface to retrieve “Application Status” that’s an aggregation of multiple objects. To make that happen, I created a custom Apex controller that exposes a REST endpoint. You can see below that I defined a custom class called ApplicationStatus and then a GetStatus operation that inflates and returns that custom object. The RESTful attributes (@RestResource, @HttpGet) make this a service accessible via REST query.

@RestResource(urlMapping='/ApplicationStatus/*')
global class CandidateRestService {

    global class ApplicationStatus {

        String ApplicationId {get; set; }
        String JobName {get; set; }
        String ApplicantName {get; set; }
        String Status {get; set; }
    }

    @HttpGet
    global static ApplicationStatus GetStatus(){

        //get the context of the request
        RestRequest req = RestContext.request;
        //extract the job application value from the URL
        String appId = req.requestURI.substring(req.requestURI.lastIndexOf('/')+1);

        //retrieve the job application
        seroter__Job_Application__c application = [SELECT Id, seroter__Application_Status__c, seroter__Applicant__r.Name, seroter__Job_Opening__r.seroter__Job_Title__c FROM seroter__Job_Application__c WHERE seroter__Application_ID__c = :appId];

        //create the application status object using relationship (__r) values
        ApplicationStatus status = new ApplicationStatus();
        status.ApplicationId = appId;
        status.Status = application.seroter__Application_Status__c;
        status.ApplicantName = application.seroter__Applicant__r.Name;
        status.JobName = application.seroter__Job_Opening__r.seroter__Job_Title__c;

        return status;
    }
}

With this in place – and creating an “application” that gave me a consumer key and secret for remote access – I had everything I needed to consume Salesforce.com data.

The ASP.NET Web API project

How does Twilio know what to say when you call one of their phone numbers? They have a markup language called TwiML that includes the constructs for handling incoming calls. What I needed was a web service that Twilio could reach and return instructions for what to say to the caller.

I created an ASP.NET Web API project for this service. I added NuGet packages for DeveloperForce.Force (to get the Force.com Toolkit for .NET) and Twilio.Mvc, Twilio.TwiML, and Twilio. Before slinging the Web API Controller, I added a custom class that helps the Force Toolkit talk to custom REST APIs. This class, CustomServiceHttpClient, copies the base ServiceHttpClient class and changes a single line.

public async Task<T> HttpGetAsync<T>(string urlSuffix)
        {
            var url = string.Format("{0}/{1}", _instanceUrl, urlSuffix);

            var request = new HttpRequestMessage()
            {
                RequestUri = new Uri(url),
                Method = HttpMethod.Get
            };

Why did I do this? The class that comes with the Toolkit builds up a particular URL that maps to the standard Salesforce.com REST API. However, custom REST services use a different URL pattern. This custom class just takes in the base URL (returned by the authentication query) and appends a suffix that includes the path to my Apex controller operation.

I slightly changed the WebApiConfig.cs to add a “type” to the route template. I’ll use this to create a pair of different URIs for Twilio to use. I want one operation that it calls to get initial instructions (/api/status/init) and another to get the actual status resource (/api/status).

public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            // Web API configuration and services

            // Web API routes
            config.MapHttpAttributeRoutes();

            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{type}",
                defaults: new { type = RouteParameter.Optional }
            );
        }
    }

Now comes the new StatusController.cs that handles the REST input. The first operation takes in VoiceRequest object that comes from Twilio and I build up a TwiML response. What’s cool is that Twilio can collect data from the caller. See the “Gather” operation where I instruct Twilio to get 6 digits from the caller, and post to another URI. In this case, it’s a version of this endpoint hosted in Windows Azure. Finally, I forced the Web API to return an XML document instead of sending back JSON (regardless of what comes in the inbound Accept header).

The second operation retrieves the Salesforce credentials from my configuration file, gets a token from Salesforce (via the Toolkit), issues the query to the custom REST endpoint, and takes the resulting job application detail and injects it into the TwiML response.

public class StatusController : ApiController
    {
        // GET api/<controller>/init
        public HttpResponseMessage Get(string type, [FromUri]VoiceRequest req)
        {
            //build Twilio response using TwiML generator
            TwilioResponse resp = new TwilioResponse();
            resp.Say("Thanks for calling the status hotline.", new { voice = "woman" });
            //Gather 6 digits and send GET request to endpoint specified in the action
            resp.BeginGather(new { action = "http://twilioforcetoolkit.azurewebsites.net/api/status", method = "GET", numDigits = "6" })
                .Say("Please enter the job application ID", new { voice = "woman" });
            resp.EndGather();

            //be sure to force XML in the response
            return Request.CreateResponse(HttpStatusCode.OK, resp.Element, "text/xml");

        }

        // GET api/<controller>
        public async Task<HttpResponseMessage> Get([FromUri]VoiceRequest req)
        {
            var from = req.From;
            //get the digits the user typed in
            var nums = req.Digits;

            //SFDC lookup
            //grab credentials from configuration file
            string consumerkey = ConfigurationManager.AppSettings["consumerkey"];
            string consumersecret = ConfigurationManager.AppSettings["consumersecret"];
            string username = ConfigurationManager.AppSettings["username"];
            string password = ConfigurationManager.AppSettings["password"];

            //create variables for our auth-returned values
            string url, token, version;
            //authenticate the user using Toolkit operations
            var auth = new AuthenticationClient();

            //authenticate
            await auth.UsernamePasswordAsync(consumerkey, consumersecret, username, password);
            url = auth.InstanceUrl;
            token = auth.AccessToken;
            version = auth.ApiVersion;

            //create custom client that takes custom REST path
            var client = new CustomServiceHttpClient(url, token, new HttpClient());

            //reference the numbers provided by the caller
            string jobId = nums;

            //send GET request to endpoint
            var status = await client.HttpGetAsync<dynamic>("services/apexrest/seroter/ApplicationStatus/" + jobId);
            //get status result
            JObject statusResult = JObject.Parse(System.Convert.ToString(status));

            //create Twilio response
            TwilioResponse resp = new TwilioResponse();
            //tell Twilio what to say to the caller
            resp.Say(string.Format("For job {0}, job status is {1}", statusResult["JobName"], statusResult["Status"]), new { voice = "woman" });

            //be sure to force XML in the response
            return Request.CreateResponse(HttpStatusCode.OK, resp.Element, "text/xml");
        }
     }

My Web API service was now ready to go.

Running the ASP.NET Web API in Windows Azure

As you can imagine, Twilio can only talk to services exposed to the public internet. For simplicity sake, I jammed this into Windows Azure Web Sites from Visual Studio.

2014.04.17forcetwilio04

Once this service was deployed, I hit the two URLs to make sure that it was returning TwiML that Twilio could use. The first request to /api/status/init returned:

2014.04.17forcetwilio05

Cool! Let’s see what happens when I call the subsequent service endpoint and provide the application ID in the URL. Notice that the application ID provided returns the corresponding job status.

2014.04.17forcetwilio06

So far so good. Last step? Add Twilio to the mix.

Setup Twilio Phone Number

First off, I bought a new Twilio number. They make it so damn easy to do!

2014.04.17forcetwilio07

 

With the number in place, I just had to tell Twilio what to do when the phone number is called. On the phone number’s settings page, I can set how Twilio should respond to Voice or Messaging input. In both cases, I point to a location that returns a static or dynamic TwiML doc. For this scenario, I pointed to the ASP.NET Web API service and chose the “GET” operation.

2014.04.17forcetwilio08

So what happens when I call? Hear the audio below:

[audio https://seroter.files.wordpress.com/2014/07/twiliosalesforce.mp3 |titles=Calling Twilio| initialvolume=30|animation=no]

One of the other great Twilio features is the analytics. After calling the number, I can instantly see usage trends …

2014.04.17forcetwilio09

… and a log of the call itself. Notice that I see the actual TwiML payload processed for the request. That’s pretty awesome for troubleshooting and auditing.

2014.04.17forcetwilio10

 

Summary

In the cloud, it’s often about combining best-of-breed capabilities to deliver innovative solutions that no one technology has. It’s a lot easier to do this when working with such API-friendly systems as Salesforce and Twilio. I’m sure you can imagine all sorts of valuable cases where an SMS or voice call could retrieve (or create) data in a system. Imagine walking a sales rep through a call and collecting all the data from the customer visit and creating an Opportunity record! In this scenario, we saw how to query Salesforce.com (using the Force Toolkit for .NET) from a phone call and return a small bit of data. I hope you enjoyed the walkthrough, and keep an eye out for the recorded webcast where Wade and I explain a host of different scenarios for this Force Toolkit.

Author: Richard Seroter

Richard Seroter is Director of Developer Relations and Outbound Product Management at Google Cloud. He’s also an instructor at Pluralsight, a frequent public speaker, the author of multiple books on software design and development, and a former InfoQ.com editor plus former 12-time Microsoft MVP for cloud. As Director of Developer Relations and Outbound Product Management, Richard leads an organization of Google Cloud developer advocates, engineers, platform builders, and outbound product managers that help customers find success in their cloud journey. Richard maintains a regularly updated blog on topics of architecture and solution design and can be found on Twitter as @rseroter.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.