Posted By
September 30, 2011

DocuSign and Twilio - Void Envelopes by Phone

DocuSign already gives you the option of voiding an Envelope after you've sent it (like recalling an email), but what if you could void an Envelope while on the go, without internet service? 

Now, with access to the DocuSign API and Twilio, we can grab information about Envelopes (and execute actions) from a phone call! 

Setting up

First, we'll go get the SDK samples from GitHub. We'll be using the PHP DocuSign Sample directory for most of our code. 
DocuSign SDK on GitHub

Now, we'll need to add a file to our /DocuSign-eSignature-SDK/PHP/DocuSignSample/DocuSignSample directory: 

sent_to_void.php - Lists recently sent Envelopes
You can also download the completed file at: https://github.com/nicholasareed/DocuSign-Twilio-Void-Sent-Envelope

Register with both DocuSign and Twilio

DocuSign DevCenter (Free Developer Account - follow this guide)


We need a few credentials from both services
- DocuSign: Username, Password, Integrator Key
- Twilio: Phone Number
When you have your DocuSign credentials, you need to add those to your include/account_creds.php file. Also, include the line: $AccountID = "your_account_id";

Buy a Twilio Phone Number

To get out of the Twilio Sandbox (where you have to enter a code before every call) simply input a CC number from the Account page. You don't actually need to buy any credit ($30 is free) before your account is Live. 


After logging in, navigate to the "Numbers" page and "Buy a number"


Next, click "Setup number"
Fill out the Voice Request URL to your sent_to_void.php file. In my case it is http://My_AWS_IP/ubuntu/DocuSign-eSignature-SDK/PHP/DocuSignSample/sent_to_void.php

Coding the Application

There are three parts to this application:
- Read out ("Say") the Sent Envelopes
- Confirm Voiding
- Void the Envelope

 

Utility Function

	function getAPI() {
	    global $api_endpoint;
	    global $IntegratorsKey;
	    global $UserID;
	    global $Password;
	    
	    $api_wsdl = "api/APIService.wsdl";
	    $api_options =  array('location'=>$api_endpoint,'trace'=>true,'features' => SOAP_SINGLE_ELEMENT_ARRAYS);
	    $api = new APIService($api_wsdl, $api_options);
	    $api->setCredentials("[" . $IntegratorsKey . "]" . $UserID, $Password);
	    
	    return $api;
	}
	
	function getFolderItems() {
	  global $AccountID;
	  
	  $api = getAPI();
		
	  // Create the folder filter to specify the scope of your search
	  // Here, we are limiting the item search to the inbox
	  // You can also limit by owner, date, status and position
	  $filter = new FolderFilter();
	  $filter->AccountId = $AccountID;
	  $filter->StartPosition = 0;
	  $filterTypeInfo = new FolderTypeInfo();
	  $filterTypeInfo->FolderType = FolderType::SentItems;
	  $filter->FolderTypeInfo = $filterTypeInfo;
		
	  // Send
	  $getFolderItemsparams = new GetFolderItems();
	  $getFolderItemsparams->FolderFilter = $filter;
	  $response = $api->GetFolderItems($getFolderItemsparams);
		
	  return $response;
	}
	
	
	function compareSentTime($a, $b){
		$a = strtotime($a->Sent);
		$b = strtotime($b->Sent);
		if ($a == $b) {
		    return 0;
		}
		return ($a < $b) ? 1 : -1;
	}
	
	
	function timeAgo($date,$granularity=1) {
	  $date = strtotime($date);
	  $difference = time() - $date;
	  $periods = array('decade' => 315360000,
	      'year' => 31536000,
	      'month' => 2628000,
	      'week' => 604800, 
	      'day' => 86400,
	      'hour' => 3600,
	      'minute' => 60,
	      'second' => 1);
	  $retval = null;
	  
	  if($difference == 0){
	  	return 'a moment';
	  }
	  
	  foreach ($periods as $key => $value) {
	      if ($difference >= $value) {
	          $time = round($difference/$value);
	          $difference %= $value;
	          $retval .= ($retval ? ' ' : '').$time.' ';
	          $retval .= (($time > 1) ? $key.'s' : $key);
	          $granularity--;
	      }
	      if ($granularity == '0') { 
	      	break; 
	      }
	  }
	  return $retval;      
	}
	
	
	function voidEnvelope($envelopeID = '') {
	  $api = getAPI();
		
		$voidEnvelopeparams = new VoidEnvelope();
		$voidEnvelopeparams->EnvelopeID = $envelopeID;
		$voidEnvelopeparams->Reason = "Voided from phone call"; // Turn into transcription
		$response = $api->VoidEnvelope($voidEnvelopeparams);
		
	  return $response;
	}
		

Step 1: Read Sent Envelopes

	// Get Envelopes Sent
	$allSent = getFolderItems();
$sent = array();
foreach($allSent->GetFolderItemsResult->FolderItems->FolderItem as $item){
// Get only the actually Sent ones
if($item->Status == 'Sent'){
$item->TimeAgo = timeAgo($item->Sent,1);
array_push($sent,$item);
}
}

// Sort by Sent time
usort($sent, "compareSentTime");

// Output TwiML
header("content-type: text/xml");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<Response>
<Gather action="sent_to_void.php?step=2" numDigits="1" method="POST">
<?
$i = 1;
foreach($sent as $item){
?>
<Say>Press <?= $i; ?> for <?= $item->Subject; ?>. Sent <?= $item->TimeAgo;?> ago.</Say>
<?
$i++;
if($i > 9){
break;
}
}
?>
</Gather>
</Response>

Step 2: Confirm Voiding

 	// Get the Digit
if(strlen($_POST['Digits']) != 1){
header("Location: sent_to_void.php");
die;
}

if($_POST['Digits'] < 1 || $_POST['Digits'] > 9){
header("Location: sent_to_void.php");
die;
}

$digit = $_POST['Digits'];

// Get Envelopes Sent
$allSent = getFolderItems();
$sent = array();
$i = 1;
foreach($allSent->GetFolderItemsResult->FolderItems->FolderItem as $item){
// Get only the actually Sent ones
if($item->Status == 'Sent'){
$item->TimeAgo = timeAgo($item->Sent,1);
array_push($sent,$item);
$i++;
}
}

// Sort by Sent time
usort($sent, "compareSentTime");

// Get the right Envelope
$envelope = array();
$i = 1;
foreach($sent as $item){
if($i == $digit){
$envelope = $item;
}
$i++;
}

if(empty($envelope)){
header("Location: sent_to_void.php");
die;
}

// Read the Envelope Name, if it is correct, then remove it
// - Do you want to Void the Envelope "dkflj" sent "2 days ago"
// - Press 1 to confirm
// - Press 0 to go back to the previous menu

// Output TwiML
header("content-type: text/xml");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<Response>
<Gather action="sent_to_void.php?step=3&amp;id=<?= $envelope->EnvelopeId; ?>" numDigits="1">
<Say>Press 1 to confirm voiding the Envelope. <?= $envelope->Subject; ?>. Sent <?= $item->TimeAgo; ?> ago.</Say>
</Gather>
</Response>

Step 3: Void the Envelope

 	if($_POST['Digits'] != "1"){
header("Location: sent_to_void.php");
die;
}

// Get the envelopeID
$envelopeID = $_GET['id'];

// Void the Envelope
$response = voidEnvelope($envelopeID);

// Output TwiML
header("content-type: text/xml");
echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
?>
<Response>
<Say>Envelope has been voided. Thank you. Goodbye. </Say>
</Response>

Debugging

If you are having trouble getting this sample to work, try using GET requests and debug using your browser (just request the page like a normal webpage). 

Things Left Out

- Verify that the request is coming from Twilio. To do that, check out Twilio Security  
- Protect with an Access Code. Before listing the Envelopes, we should ask for an Access Code to be typed in (before Step 1) 

Have questions? Check out our Community Forums or the GitHub Wiki!