At the excellent SF Developer Advocates Meetup this week, I met Johnny Diggz, Chief Evangelist for Tropo, a cloud API for Voice and SMS. Chatting about how you could integrate Tropo and Force.com, Diggz mentioned that Tropo supports speech recognition – it can post a transcription of a voice message to a URL. No prizes for guessing the first thought that came into my head: “I bet I can build an integration here – look up a contact based on the caller ID and create a Task with the message transcription.” (Completed Tasks appear in a Contact’s Activity History, just like when you ‘Log a Call’ in the browser.)

Here is a run through of how I got this working, and some ideas for taking it further.

First, I signed up for Tropo and created an application, assigning it a phone number. I used the Tropo transcribing sample as the basis for a hosted JavaScript file that executes on their infrastructure when my number receives a call. Here it is:

var callerID = currentCall.callerID;

record("Please leave a message!", {
    beep:true,
    maxTime:120,
    transcriptionOutFormat: "json",
    transcriptionOutURI: "http://some.url.com/",
    transcriptionID:callerID
    }
);

When the call connects, my app says “Please leave a message!”, beeps, and the caller has two minutes of message time. When the call completes, the incoming caller ID and a transcription of the call is JSON encoded and sent (via HTTP POST) to the given URL. I used the indispensable RequestBin to test this out: I called my app’s number, left a message, and this is what my bin received (edited to remove my phone number!):

{ "result": { "guid": "c6130310-cfd8-012f-0b73-12313d064a99", "identifier": "1234567890", "transcription": "Pat leaving a message" } }

So far so good. Now I used Simon Fell‘s JSON2Apex to create an Apex class to model that JSON – copy, paste, and voilà:

//
// Generated by JSON2Apex http://json2apex.herokuapp.com/
//
public class Transcription {
	public class Result {
		public String guid;
		public String identifier;
		public String transcription;
	}

	public Result result;

	public static Transcription parse(String json) {
		return (Transcription) System.JSON.deserialize(json, Transcription.class);
	}

	static testMethod void testParse() {
		String json = '{ \"result\": { \"guid\": \"c6130310-cfd8-012f-0b73-12313d064a99\", \"identifier\": \"superpat7\", \"transcription\": \"Dan leaving a message\" } }';
		Transcription obj = parse(json);
		System.assert(obj != null);
	}
}

The next step was creating an endpoint on Force.com to receive message transcriptions. First, I defined an Apex class as a REST endpoint. I’ll walk through it step by step here; you can also grab the complete Apex class.

Let’s start with the class definition:

@RestResource(urlMapping='/messageToTask')
global class MessageToTask {

That gives me an endpoint at /services/apexrest/messageToTask on a Force.com Site. Now, since the incoming JSON object has a single property, ‘result’, I need only paste the corresponding class as an inner class in MessageToTask:

    global class Result {
        public String guid;
        public String identifier;
        public String transcription;
    }

Next I need to define a method that will handle the HTTP POST. Here is the signature, with the relevant annotation:

    @HttpPost
    global static void incomingMessage(Result result) {

Conveniently, the incoming message is parsed by the platform, and I’m given a Result object, ready to use. Tropo gives me the incoming caller ID as unadorned digits, so I need to format it before I can query for a matching Contact:

        // Format the incoming phone number as (999) 999-9999
        // TODO - handle international formatting
        String phone = '(' + result.identifier.substring(0,3) + ') ' +
            result.identifier.substring(3,6) + '-' +
            result.identifier.substring(6,10);

As you can see, this will work for US numbers only – I left international formatting for another day! ;-) The next step is to locate a contact from this phone number:

        // Try to find a matching Contact
        Contact contact = null;
        try {
            contact = [SELECT Id FROM Contact WHERE (Phone = :phone OR MobilePhone = :phone) LIMIT 1];
        } catch (QueryException qe) {
            System.debug('No match for phone number '+result.identifier);
            return;
        }

I’m just matching on the main and mobile phone numbers for Contacts here, but you could match on additional fields, match against Leads, custom objects whatever you like. For simplicity, If I don’t find a match, I simply write a debug message and exit, but you could create a new Lead with the phone number, or whatever makes sense in your environment.

With a Contact ID in hand, it’s a snap to populate a Task record with the relevant data and insert it in the database:

        // Create and insert a new Task
        Task task = new Task(ActivityDate = Date.today(),
            CallDisposition = 'Contact left a message',
            CallType = 'Inbound',
            Description = result.transcription,
            Status = 'Completed',
            Subject = 'Phone Message from '+phone,
            Type = 'Call',
            WhoId = contact.Id);
        insert task;

And that’s it – all done, in less than 40 lines of Apex.The last step was to create a Force.com Site, and make the MessageToTask endpoint publicly visible, as described in detail here. That gave me a publicly accessible endpoint with a URL of the form https://somesite.force.com/services/apexrest/messageToTask, which I copied into my snippet of JavaScript as the transcriptionOutURI. A couple of quick calls confirmed that all was working as expected!

Here are a couple of messages logged against a Contact:

And here is the detail of one of those:

Now that the Task is saved against the Contact, you’d likely want some Workflow or a Trigger to notify the Contact’s owner that a new message has arrived and, presumably, demands some attention. I’ll leave that as an exercise for the reader :-)

Note that this is a very basic solution – as it stands, you would need to think about how to authenticate the incoming message. Given that the messages are exchanged over a secure transport, and thus are secure against eavesdropping, a simple shared secret might suffice. You could append a separator and the shared secret to the callerID in the transcriptionID, then parse it out in the Apex class and validate it. There are also other integration approaches that would produce a more secure implementation – watch this space!

What have you integrated with Force.com recently? Let us know in the comments!

 

 

Bookmark the permalink. Trackbacks are closed, but you can post a comment.