Slack is an amazing tool, there’s no denying it. A lot of great chat bots and applications have been created around this productivity tool making it even more awesome. Very recently, OttSpot was launched, making voice communication and customer relations management within Slack ever so simple.

This gave me an idea, let’s integrate the Voxbone Provisioning API to Slack to make it super simple for all of the productivity freaks out there to order and configure phone numbers within the Slack app – why not, maybe even one day be able to launch a call from the app (that will be for another post!).

[UPDATE] Part 2 is here!

voxslack

No more wasting your time, let’s dive right into automating your DID provisioning within Slack with less than 150 lines of code!

Here are a few things that you will need:

Here are a few additional resources in case you get lost:

Setting up the Slack Command

Today we will be building the first part: automating the buying of a DID. Let’s start with the easy part, no code required, just your Slack group and a minute or so.

When you visit https://api.slack.com/custom-integrations, you have the option to “Set up a Slash Command” which will ask you to log in and will prompt you the following:

slackcmd

Let’s call our Slack command “/did”. When you add that command you’ll be asked to fill out other things like URL (which we will point to our Heroku app later), Method (which is a POST), and a description box (can be something like “this is a command to order DIDs”).

The command will look something like this:

cmd

Which orders DIDs based on [Country], [City], [DID Type], [Features], and [Quantity] specified by the Slack user.

The Code

If you are already familiar with Slack, you will know that these commands actually work by sending a POST request to an external URL (like a mini API). So we will be building a mini API using NodeJS that fetches the information from the VoxAPI using the parameters specified by the Slack user and returning the confirmation of purchase.

This code will be hosted on Heroku so as soon as you have your app URL you can update the Slack command with it!

The structure of the code sample is very simple. Essentially, we only need a package.json file with the required NPM dependencies like “request”, and an app.js where our code will go!

1. Initializing our app.js file

A basic expressJS app looks like the following:

var express = require('express');
var path = require('path');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var request = require('request');

var app = express();

// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));

app.post('/', function(req, res){
// Code and things go in here to build an API to /POST info to!
});

// catch 404 and forward to error handler
app.use(function(req, res, next) {
    var err = new Error('Not Found');
    err.status = 404;
    next(err);
});

module.exports = app;

What we’ll be doing is add programming logic and interaction with the VoxAPI within app.post(‘/’, function(req, res){}); which will be reachable from the Slack channel.

2. Adding some general variables

Before updating our app.post code block, let’s add some general parameters (before app.post) that can be reused to easily make requests to the API.

//This is the base URL for all requests to the VoxAPI. When ready for production change to api.voxbone.com
var url = 'https://sandbox.voxbone.com/ws-voxbone/services/rest/';

//This is the headers that will be used for all API requests
var headers = {'Accept': 'application/json','Content-type': 'application/json'};

//Add your own credentials!
var auth = {'user': 'your_voxapi_username', 'pass': your_voxapi_password'}

3. Securing the endpoint and fetching the parameters specified by the Slack user

Slack gives you a lot of information when posting to your external URL. Let’s have a look:

token=gIkuvaNzQIHg97ATvDxqgjtO
team_id=T0001
team_domain=example
channel_id=C2147483705
channel_name=test
user_id=U2147483697
user_name=Steve
command=/weather
text=USA, NEW YORK, GEOGRAPHIC, voxsms, 1
response_url=https://hooks.slack.com/commands/1234/5678

The token can be used to authenticate your application and perform the DID provisioning only if this token is present in the request (ie. if it’s really coming from that Slack channel, and only that Slack channel).

Also, Slack sends you the text that is inserted after the /did command. We will parse this text to extract the valuable parameters to make the various API requests. To parse we will use the nifty split(‘separator’) javascript function which creates an array of those separated parameters in one line!

if (req.body.token == ‘gIkuvaNzQIHg97ATvDxqgjtO’){
  //Get the incoming string of text from Slack
  var string = req.body.text;
  
  //Split the string into parameters separated by a comma and a space	
  var parameters = string.split(', ');

  //Assign each variable to its respective Slack text parameter
  var country = parameters[0];
  var city = parameters[1];
  var type = parameters[2];
  var feature = parameters[3];
  var quantity = parameters[4];

  //We will also use the response_url for reasons to be developed on later!
  var response_url = req.body.response_url;

  //On success, launch a series of callback functions that will use these parameters to interact with the VoxAPI
  searchDid(0,1,country, city, type, feature, quantity, response_url);
} else{
  //If the token is not present, return some kind of message
  res.status(200).send('You are not authorized to reach this endpoint!');
}

4. Searching for the DID

In the previous section we use searchDid(0,1,country, city, type, feature, quantity, response_url); to initiate a series of callback functions to interact with the VoxAPI, let’s dive into that deeper. Before we start, it would be good to review the proper flow of ordering DIDs through our API so you get familiar with it.

Now we’ll actually create the first function: “searchDid” which will look for a DID using the specified parameters: pageNumber, pageSize, countryCodeA3, cityNamePattern, didType, featureIds and a few others for the callback functions that will come after that: quantity and response_url. The listDidGroup method of the VoxAPI allows for a lot of different search parameters. Once you understand better how this Slack command works, you can modify this code and add other kinds of parameters. Just check the VoxAPI documentation for List DID Group.

After your if statement, create that searchDid function like so:

function searchDid(pageNumber, pageSize, countryCodeA3, cityNamePattern, didType, featureIds, quantity, response_url){
  //First we translate the specified voxsms or voxfax parameter into a proper feature ID which the VoxAPI uses, respectively 25 and 6
  var fid = getFeatureId(featureIds);
  function getFeatureId(featureIds){
    if (featureIds = null){
      fid = NULL;
    } else if (featureIds = 'voxsms'){
      fid = 25;
    } else if (featureIds = 'voxfax'){
      fid = 6;
    }
    return fid;
  };

  //Now we set up the options that will be sent in the request using the URL declared previously, your auth information, and adding the query parameters to the URL
  var options = {
    url: url+'inventory/didgroup',
    headers: headers,
    "auth": auth,
    qs : {
      "pageNumber" : pageNumber,
      "pageSize" : pageSize,
      "countryCodeA3" : countryCodeA3,
      //the % sign is used in VoxAPI to specify the pattern to be searched, New Yo would work just as well as New York thanks to this
      "cityNamePattern": cityNamePattern+'%',
      "didType": didType,
      "featureIds": fid
    } 
  };
  //Now we can launch the actual search request to the VoxAPI
  request.get(options, function (error, response, body) {
    if (!error && response.statusCode == 200) {
      var body = JSON.parse(body);

      //We recover the DIDGroupID of the first DID returned by the API to use it later when adding to the cart.
      var didid = body.didGroups[0].didGroupId;
      console.log('[DEBUG] - DID found: '+didid);

      //As per the proper flow of ordering DIDs, we will create a new cart and pass the didGroupId, quantity, and response_url to the callback function for later use
        createCart(didid, quantity, response_url);
    } else {
       var body = JSON.parse(body);
       res.setHeader('Content-Type', 'application/json');
       
       // In case the request is unsuccessful, we return the error message coming from the VoxAPI to the Slack Channel
	res.status(200).send('could not find DID matching those criteria! '+ body.errors[0].apiErrorMessage);
    }
});
}

The crazy thing here is that we’ve already written over half of the 150 lines of code I told you we would write!

5. Create the Cart

Now you’ve got a DID, let’s start the purchasing process! If you checked the guide on the proper ordering flow using VoxAPI, you would have seen that the next logical step is to create the cart where we’ll add the DID we just found.

In the previous section we initialized the callback function  createCart(didid, quantity, response_url) which we will declare now:

The createCart API call requires you to set a customer reference and description of your choice.

// Create Cart
function createCart(didid, quantity, response_url){
  //Here we set a random customer reference number
  var cr = Math.floor((Math.random() * 100) + 1);
  var description = "cart #: " + cr;
  var options = {
	url: url+'ordering/cart',
	headers: headers,
	"auth": auth,
	body: JSON.stringify({ customerReference : cr, description : description }) 
  };
  //Here’s the actual request to create the cart
	request.put(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
          var body = JSON.parse(body);
	  //We retrieve the cart identifier number from the VoxAPI to use in the addToCart() method
          var cartId = body.cart.cartIdentifier;
          console.log('[DEBUG] - Cart Created: #'+cartId);
          //This is the callback function to add the DID we just found to the cart we just created, with a specified quantity. We also pass the response_url (more on that later)
           addToCart(didid, quantity, cartId, response_url);
        } else {
	  //If something goes wrong, we send back to the Slack Channel a notification
          res.setHeader('Content-Type', 'application/json');
	  res.status(200).send('could not create cart!');
        }
    });
}

6. Add the DID to the Cart

Now that we have a DID and a cart, we can add that DID to the cart using the AddToCart() method of the API.

Let’s declare the addToCart() function we used in the previous section:

//Add to Cart
//We pass the DIDGroupId of the DID we searched for, the cartID of the cart we created, and the quantity (how many of those DIDs we’ll purchase.
function addToCart(didid, quantity, cartId, response_url){
	var options = {
		url: url+'ordering/cart/'+cartId+'/product',
		headers: headers,
		"auth": auth,
		body: JSON.stringify({ didCartItem : {didGroupId : didid, quantity : quantity}}) 
	};
	request.post(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
            console.log('[DEBUG] - '+quantity+' DIDs of didGroup #'+didid+' added to Cart #'+ cartId);
//Checking out the cart is the next logical step, for this we only need the cart ID which is filled with the DIDs we wish to purchase. We also pass the response_url (I promise I’ll get to that before the end!)
            checkoutCart(cartId, response_url);
        } else {
	//Again, if something goes wrong, we notify the Slack Channel
        	res.setHeader('Content-Type', 'application/json');
	res.status(200).send('could not add to cart!');
        }
    });
}

7. Checking out the Cart

We are finally ready to checkout the cart we created that is filed we all the DIDs we want! Let’s dive into that in depth:

//Checkout Cart
function checkoutCart(cartId, response_url){
	var options = {
		url: url+'ordering/cart/'+cartId+'/checkout',
		headers: headers,
		"auth": auth,
		qs : {
	        "cartIdentifier" : cartId
    	} 
	};
	request.get(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
        	var body = JSON.parse(body);
	//Here’s a warning handler in case something goes wrong, it detects whether the status return from VoxAPI is ‘WARNING’
            if (body.status == 'WARNING'){
            	var message = body.productCheckoutList[0].message;
		sendResponse(message, response_url);
		res.setHeader('Content-Type', 'application/json');
		res.status(200).send('There was a problem ordering your DID.');
            } else{
		//If there are no problem, every thing is fine - the DID will be purchased, and we send back an order reference
        		console.log("Your DID has been purchased and your order reference # is: "+body.productCheckoutList[0].orderReference);
        		var message = "Your DID has been purchased and your order reference # is: "+body.productCheckoutList[0].orderReference;
        		res.setHeader('Content-Type', 'application/json');
		res.status(200).send('DID ordered! Confirmation on its way.');
		//Here’s where it gets interesting - more on it below!
	        	sendResponse(message, response_url);

            };
        } else {
        	res.setHeader('Content-Type', 'application/json');
			res.status(200).send('could not checkout cart!');
        }
    });
}

I promised you I would get to response_url before the end. If everything worked according to the plan, once we checkout the cart, the DID is purchased and we can send back an order confirmation to the Slack channel like all other notifications using

res.setHeader('Content-Type', 'application/json');

res.status(200).send('Your DID has been purchased and your order reference # is: "+body.productCheckoutList[0].orderReference');

However, because of all this callback hell, it takes a while to process all the requests all the way to that message and send it back to the Slack channel – moreoever, the Slack command hook only allows for a 3000ms delay when relaying back the information. So we need to devise another plan: delayed response.

Fortunately, Slack allows you to send back a delayed response to the channel using the response_url and doing a /POST request to it. That’s what we will do, using sendResponse(message, response_url) that we just called

8. Sending the Confirmation back to Slack

Here we go! We now have our DIDs ordered, we just need to let the Slack user know that everything went well and send back a confirmation order using Slack’s delayed response functionality. Here’s the code:

function sendResponse(message, response_url){
	var options = {
		url: response_url,
		headers: headers,
		body: JSON.stringify({ text : message }) 
	};
	//Here we are actually making a post request to the Slack response_url instead of the VoxAPI endpoints and we pass back the message that we created in the previous section.
	request.post(options, function (error, response, body) {
        if (!error && response.statusCode == 200) {
        	console.log(response);
        } else {
        	console.log(response);
        }
    });
}

Pushing to Heroku and Testing!

The backend part of our application is finished! We can know host it on Heroku, update the Slack Command with our app’s Heroku URL, and start testing!

Here’s how to push to Heroku (hopefully you’ve installed Heroku CLI by now)

//cd into the repository that holds the app we just created (eg. voxslack)
cd voxslackbot

//Initialize git
git init

//Create a Heroku app. Heroku create will provide us with the link for the Sack Command configuration

heroku create

//Add and commit
git add .
git commit -m ‘deploy’

//Push to Heroku. 
git push heroku master

//Your application is now live! Let’s keep track of the logging and see all the debug information we added in our app to see if it works correctly when using Slack

heroku logs --tail

//Now that your application is live, add your Heroku app URL int the configuration page of the Slack command you created.

Now we open our Slack Channel and test it out!

Send a message like so in the chat and enjoy the magic: /did USA, NEW YORK, GEOGRAPHIC, voxsms, 1

Try some other combinations like TOLLFREE for DID type, or voxfax for features or 10 for quantities.

errors

When you are happy with the results, just change the VoxAPI endpoint in the app to api.voxbone.com (instead of sandbox.voxbone.com) and push it again:

git add .
git commit -m 'deploy'
git push heroku master
heroku logs --tail

You are now ready to order DIDs faster than anyone on the planet! Checkout our the second part of this tutorial on configuration!