12
OCT 2011

Posted by Nicole at 12:35 PM EDT

4011 reads

Share this

OnSIP Team Attends Voxeo Customer Summit And Builds OnSIP-Tropo Whisper Application

Rooted heavily in SIP standards and customer service values, Voxeo has been a company we've known about and respected for years. Since it was founded in 1999, Voxeo has been mainly focused on hosted and on-premise based IVR solutions for customers much larger than ours. Likewise, our business rarely intersected with Voxeo’s. However, in 2009, Voxeo launched a new product called Tropo, a cloud-based application platform that makes it easy for web developers to build voice applications using familiar languages. This product is extremely promising for us, and it also gave us a great excuse to head down to Orlando for Voxeo’s Customer Summit. As our customer size is growing, the demand for custom applications is also growing. Tropo presents a good opportunity for us to satisfy that custom application need.

Challenge: Build an OnSIP-Tropo Whisper application WHILE at the summit

Dabbling with the Tropo platform, John Riordan, CTO, and I created a goal while at the Voxeo Summit: Code and write about a new OnSIP-Tropo application by the end of the summit. The thought here was that if John can manage to write an application by the pool, we can truly express how easy it is to build an OnSIP-Tropo application. And so, we decided to build a whisper application. Popularized by Google Voice, a whisper application is where a caller makes a call to the callee, and the callee then is presented with the choice to accept or decline the call. If the callee declines the call, the call is sent to a preset destination (e.g. callee’s OnSIP voicemail.) The beauty of working with OnSIP and Tropo is that both services have a similar convention where all applications and end-points have SIP addresses. As such, the call from OnSIP to the Tropo application and back to OnSIP voicemail are all SIP calls – free! And so, we began:

DAY 1:

John spent most of the day by the pool and left an hour for coding, to my discomfort. He explained, over his pina colada, that it was alright as he had faith in Tropo. During his hour of coding and testing his application, things were going smoothly. We discussed his innovative way to build a Whisper app using Tropo’s conference bridge. Later that evening, we attended the kick off reception and ran into Justin Dupree, Technical Writer at Voxeo Labs. Justin explained that just this past month, he built that same whisper application and blogged about it here. We took this as a sign that great minds think alike.

DAY 2:

John spent most of the day listening to Voxeo discussions, but he did get a little coding in here and there. He became suddenly perturbed at a bug he had found during testing. The problem was this: In the application, a call comes in, is answered, and is then placed in the conference bridge. When the call is in the conference bridge, we allow signals such as exit. From an outbound call script, we call the REST API with the session ID associated to the conference bridge and the signal to exit. Whereas yesterday it had been working by the pool, it now wasn’t working at all. After about an hour, John realized that when he tested it more slowly, it actually did work most of the time. (There seemed to be some lag in Tropo’s REST API's access to the conference bridge, causing our exit signal with the conference bridge session ID to fail.) Lesson learned here, as a lead Voxeo engineer pointed out later in the evening, may be that coding with Tropo and a pina colada is a good idea. However, we will note that even testing slowly today, the signal sometimes fails to get caught by the conference bridge. Nevertheless, after a few hours, John finished an application before the 6:30 PM party at the plaza, wherein we got a nice tour of the Voxeo offices. (Picture of John; Voxeo CEO, Jonathan Taylor; and me below.)

DAY 3 (Today):

John completed some final testing, and we published the code, which can be found here on GitHub.

Nicole, John, & Jonathan Taylor ( Voxeo CEO)

Several parameters are configurable, including call timing, callee’s destination phone number or SIP address, messages that Tropo plays to the callee and caller, and more. Here are a few:

// the callee for the outbound call leg $outboundCallee = "sip:you@yourdomain.onsip.com"; // voicemail (set to 'NULL' for no voicemail) $outboundVoicemail = "sip:voicemail@yourdomain.onsip.com"; // the Tropo voice outbound token (effectively a password) $outboundToken = 'your-token'; // messages played to the inbound caller $inboundCallerHoldMessage = "Please hold while we connect your call."; $inboundCallerDeclinedMessage = "Your call has been declined. Goodbye."; $inboundCallerVoicemailMessage = "Your call has been declined. Please wait while we transfer you to voicemail.";

The application then works as follows: When an inbound call comes in, it is placed in a conference. Before putting the call in a conference, this same script is launched via the Tropo API which then makes an outbound call to the callee. If the callee answers, the callee is prompted to accept the call. If the callee accepts, the callee is also put in the conference, thus 'bridging' the two call legs. If the callee declines or does not answer, the caller is removed from the conference by yet another call to the Tropo REST API to send an event that signals the caller exit the conference. At that point, the caller is sent to voicemail if a failover address is provided; otherwise, the caller is hung up on.

<?php//// Tropo Whisper App//
// A conference is used as a 'bridge' to implement a whisper application where a contact is provided with
// the option of accepting or declining an inbound call. When an inbound call comes in it is placed in a
// conference. Before putting the call in a conference this same script is launched via the Tropo API which
// then makes an outbound call to the callee. If the callee answers they are prompted to accept the call. If
// they accept they are also put in the conference thus 'bridging' the two call legs. If the callee declines
// or does not answer, the caller is removed from the conference by yet another call to the Tropo REST API
// to send an event which signals the caller exit the conference. At that point the caller is sent to
// voicemail if a failover address is provided otherwise the caller is hung up on.
//the callee for the outbound call leg
$outboundCallee = "sip:you@yourdomain.onsip.com";
// voicemail (set to 'NULL' for no voicemail)
$outboundVoicemail = "sip:voicemail@yourdomain.onsip.com";
// the Tropo voice outbound token (effectively a password)
$outboundToken = 'your-token';
// number of seconds to wait for call to be answered
$outboundCallTimeout = 30;
// number of attempts the callee will be given to respond
$outboundAskAttempts = 2;
// number of seconds to wait for a response from the callee
$outboundAskTimeout = 5;
// message played to the oubound callee
$outboundCalleeDeclinedMessage = "You have declined the call. Goodbye.";
$outboundCalleeBadChoiceMessage = "I'm sorry, I didn't get that. Please say yes or no";
// the URL used to signal the inbound caller to exit the conference
$outboundCurloptURL = 'https://api.tropo.com/1.0/sessions/' . $session . '/signals?action=signal&value=exit';
$outboundCurloptConnecttimeout = 2;
// messages played to the inbound caller
$inboundCallerHoldMessage = "Please hold while we connect your call.";
$inboundCallerDeclinedMessage = "Your call has been declined. Goodbye.";
$inboundCallerVoicemailMessage = "Your call has been declined. Please wait while we transfer you to voicemail.";
// the URL of this application which is used to trigger the outbound call
$inboundCurloptURL = 'http://api.tropo.com/1.0/sessions?action=create' . '&token=' . $outboundToken . '&caller=' . $currentCall->callerID . '&session=' . $currentCall->sessionId;$inboundCurloptConnecttimeout = 2;
// the id of the conference room to use as a 'bridge'.
$conferenceId = "1234";
// signal the caller to exit the conference bridge by sending an 'interrupt'.
function signalConferenceExit()
{ global $outboundCurloptURL;
global $outboundCurloptConnecttimeout;
$curl_handle=curl_init();
curl_setopt($curl_handle, CURLOPT_URL, $outboundCurloptURL);
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, $outboundCurloptConnecttimeout);
curl_exec($curl_handle);
curl_close($curl_handle);}
// choice callback// - callee made a valid selection
// - if 'yes', connect callee to conference
// - if 'no', disconnect caller and provide feeback to callee

function onChoice($event) {
global $outboundCalleeDeclinedMessage;
global $conferenceId;
// if the callee accepted the call put the callee into the conference
if ($event->value == "yes")
{ conference($conferenceId); }
// else if the callee declined the call
else if ($event->value == "no") {
// provide feedback to the callee that they have declined the call
say($outboundCalleeDeclinedMessage); sleep(15);
// workaround for timing bug in tropo (this line should not be needed)
// signal the caller to exit the conference bridge by sending an 'interrupt'.
signalConferenceExit(); }
// should never get here
else { say("An error has occurred, but this one never happens. Goodbye."); }}

// bad choice callback// - callee made an invalid selection// - let them know about it
function onBadChoice($event) {
global $outboundCalleeBadChoiceMessage;
say($outboundCalleeBadChoiceMessage);}
// error callback
// - some error occured
// - disconnect the caller
function onError($event)
{ sleep(15);
// workaround for timing bug in tropo (this line should not be needed)
signalConferenceExit();}
// hangup callback
// - callee hungup
// - disconnect the callerfunction
onHangup($event) { sleep(15);
// workaround for timing bug in tropo (this line should not be needed)
signalConferenceExit();}
// timeout callback
// - timed out waiting on callee
// - disconnect the callerfunction
onTimeout($event) { sleep(15);
// workaround for timing bug in tropo (this line should not be needed)
signalConferenceExit();}
//// Main//
// assume that caller-id indicates this is an inbound call so make an outbound call to the callee
if ($currentCall->callerID) { //
// handle incoming leg //
// we trigger an outbound call to the callee via a request to the tropo api
$curl_handle=curl_init();
curl_setopt($curl_handle, CURLOPT_CONNECTTIMEOUT, $inboundCurloptConnecttimeout);
curl_setopt($curl_handle, CURLOPT_URL, $inboundCurloptURL);
curl_exec($curl_handle);
curl_close($curl_handle);
// tell the caller to hold on while we wait on the callee
say($inboundCallerHoldMessage);
// put the caller into the conference
conference($conferenceId, array("allowSignals" => "exit"));
// the caller will be booted from the conference if the callee declines,
// so let the caller know that they have been rejected and then hangup.
say($inboundCallerVoicemailMessage);
// transfer($outboundVoicemail, array("timeout" => $outboundCallTimeout, "callerID" => $caller));
if($outboundVoicemail) {
say($inboundCallerVoicemailMessage);
transfer($outboundVoicemail, array("timeout" => $outboundCallTimeout, "callerID" => $caller)); }
else { say($inboundCallerDeclinedMessage); }}
// otherwise handle an incoming call
else { //
// handle outgoing leg //
call($outboundCallee, array("timeout" => $outboundCallTimeout, "callerID" => $caller, "onTimeout" => "onTimeout", "onCallFailure" => "onError"));
// say 'Hi" to the callee
say("Hi");
// and wait a second to make sure the RTP has been setup
sleep(1);
// query the callee in regards to their desire to answer the call
ask("Do you want to be connected to " . $caller ."?", array("choices" => "yes, no", "attempts" => $outboundAskAttempts, "timeout" => $outboundAskTimeout, "onChoice" => "onChoice", "onBadChoice" => "onBadChoice", "onError" => "onError", "onHangup" => "onHangup", "onTimeout" => "onTimeout"));}?>


Voxeo Customer Summit – A success!