Getting Started

Slack is a cloud-based team collaboration tool that has changed over the past years the way businesses communicate. Sure, we can send and receive instant messages but what makes Slack different is its ability to bring third-party tools into chat. This allows users to gather different used tools

For this, Slack provides a great set of integrations with tools like Github for source control and Travis CI or Circle CI for continuous integration to quote a few. This way we are notified of key events (PR creations, build failures, …) and can react to them. This makes teams more efficient and reactive.

In this guide, we will describe how to implement custom integrations with Slack by leveraging its APIs. Let’s start by providing an overview of the Slack integration programming model.

Understanding foundations

Slack provides different mechanisms allowing us to interact with it in both directions:

  • Querying Slack from outside to get hints in a synchronous way
  • Sending messages from outside and receive an acknowledgment
  • Being notified of events
  • Notifying outside from Slack

The following figure summarizes all these mechanisms and their communication direction(s).

As you can see, different parts of the Slack API. This is mainly because of the publish / subscribe model implemented by the tool.

  • Web API consists of a set of HTTP RPC-style methods to execute simple to advanced calls on Slock
  • Real Time Messaging API is a WebSocket-based API that allows you to receive events from Slack in real time and send messages as users
  • Events API allows us to configure Webhooks to call when events occur
  • Incoming webhooks to make Slack expose public URL to send messages
  • Slash Commands to trigger URL calls from chat UI
  • Outgoing webhooks to trigger external URLs on messages in real time

As you can see, many ways are provided to interact with Slack according to your needs. Let’s start with the way to send messages.

Sending messages

Sending messages is probably the first thing you want to do. There are two ways to do this programmatically in Slack: with incoming webhooks or using the Web API. In this section, we will focus on the second approach.

Using the Web API

To be able to send messages through the Web API, we need to leverage the chat.postMessage method. The basic accepted parameters are:

  • The authentication token with the token parameter. For the guide, we will use a tester token. For this reason, all the messages sent through the API will be displayed with the user “Slack API Tester”.
  • The channel identifier with the channel parameter
  • The message text with the text parameter

As you can see, we need the channel identifier that is different to the channel name. To get this identifier, let’s use the channels.list method. Only the authentication token is necessary. As you can see below, the identifier of the general channel is C2BKTCMTL in our case.

Let’s call this method using the Postman HTTP client:

Now we have the channel identifier. Let’s leverage chat.postMessage method to send a message. We need to be careful here since the Slack Web API doesn’t support JSON format as input but form structure whereas the documentation describes message structures with this format.

These form parameters can be passed either in the URL or the payload.

The hello world message is displayed in the general channel.

This couldn’t be simpler. That being said, the message content is perhaps too simple. Let’s improve it.

Improving content

Within the last two sections, we sent basic messages. We would obviously send something more advanced than a “hello world” message. This can be done by leveraging the message payload in the sending request.

The first good news is that Slack supports a subset of Markdown formatting. Here is a sample.

and the result:

We can also send messages with several lines using the n token. Such thing, unfortunately, doesn’t work with Postman since it automatically escapes the character. To show you this feature, we will write a small Node application leveraging the request-promise module.

var request = require('request-promise');

var options = {
  method: 'POST',
  uri: 'https://slack.com/api/chat.postMessage',
  form: {
   	token: 'xoxp-79653476964-...',
    channel: 'C2BKTCMTL',
    text: 'A message\non several\nlines.',
  },
  headers: {
    'content-type': 'application/x-www-form-urlencoded'
  }
};

request(options)
  .then(function (body) {
    // Display response content
  })
  .catch(function (err) {
    // Display errors if any
  });

A cool feature is the ability to add attachments to create even more complex messages. Our small Node application will be useful again since attachments content must be JSON-encoded into an attachments property. This can be done very easily using the JSON.stringify method.

var options = {
  method: 'POST',
  uri: 'https://slack.com/api/chat.postMessage',
  form: {
	token: 'xoxp-79653476964-...',
    channel: 'C2BKTCMTL',
    text: 'some message\non several\nlines.',
    attachments: JSON.stringify([
      {
        "text": "And here's an attachment!"
      }
    ])
  },
  headers: {
    'content-type': 'application/x-www-form-urlencoded'
  }
};

Attachments can contain many hints and have a more complex structure:

  • Header (pretext) for the text to display before the attachment
  • Title of the attachment
  • Author of the attachment
  • Color of the left border
  • Text of the attachment content. It can be on several lines
  • Fields for elements to display within an array layout
  • Images of the attachment and for the author and the footer
  • Footer of the attachment
  • Timestamp to associate a date

Let’s implement all these elements to create beautiful message in Slack:

var options = {
  (...)
  form: {
  (...)
  attachments: [
    {
        "fallback": "Required plain-text summary of the attachment.",
        "color": "#36a64f",
        "pretext": "Optional text that appears above the attachment block",
        "author_name": "Hitch",
        "author_link": "https://www.hitchhq.com/",
        "author_icon": "https://c.hitchhq.net/-/images/logo.png",
        "title": "Follow APIS at Hitch",
        "title_link": "https://www.hitchhq.com/apis",
        "text": "Optional text that appears within the attachment",
        "fields": [
            {
                "title": "Priority",
                "value": "High",
                "short": false
            },
            {
                "title": "Tag",
                "value": "Slack",
                "short": false
            }
        ],
        "image_url": "http://my-website.com/path/to/image.jpg",
        "thumb_url": "http://example.com/path/to/thumb.png",
        "footer": "from Hitch",
        "footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
        "ts": 123456789
    }
  ],
  (...)
}

Here is the result we will see in the channel:

Another feature provided by Slack is the ability to implement message buttons. This allows us to implement workflows directly within the Slack UI. We don’t describe here but it will be the subject of another guide.

Custom commands

In the first section, we described communication from a third-party application to Slack. We now deal with the other direction: from Slack to a third-party application. There are several ways to do that with Slack. Users can explicitly execute actions with commands or Slack can trigger actions based on events. We will focus here on the first approach.

We will describe how to implement a simple command to query Hitch to find out the three latest updates on an API.

Configuring the command

The first step consists of creating the command within Slack itself. Go to the manage page for your custom integrations. Go then to “Slack Commands” and add a new configuration.

We can fill then the details for this command in a form. Let’s specify a name, hitch in our case.

After having clicked on the “Add Slack Command Integration” button, we can fill its configuration details. The most important thing is the URL. The latter corresponds to the address to call when the command will be used.

Since we are in development, our target server is local and can be accessed through localhost. localhost can be resolved by Slack so we need an additional tool to make access our server. Several tools are available on the Web but we will use ngrok, a secure introspectable tunnel to localhost.

After having installed it, start it with the command:

$ ./ngrok localhost 3000
Version                       2.1.14

Region                        United States (us)
Web Interface                 http://127.0.0.1:4040
Forwarding                    http://509becc3.ngrok.io -> localhost:3001
Forwarding                    https://509becc3.ngrok.io -> localhost:3001
  
Connections                   ttl     opn     rt1     rt5     p50     p90
                      22      0       0.00    0.00    1.82    55.61                                                                                   
HTTP Requests
-------------

The address is displayed in the console. In our case: http://509becc3.ngrok.io. We are free to use a path if we want to make the server handle several commands.

Handling the request

As explained previously, when the command is executed, a request will be sent by Slack with the following payload:

token=vfGpKEBgJXj4TQhCCdE5HKaZ
team_id=T2BK7E0UC
team_domain=templth
channel_id='C2BKTCMTL
channel_name=general
user_id=U2BKLENTH
user_name='templth
command=/hitch
text=github
response_url=https://hooks.slack.com/commands/T2BK7E0UC/80505547297/jnXZriOVXonQOHbXp2WylvfP

Notice that Slack uses the application/x-www-form-urlencoded content type and not the application/json one.

To implement our server, we will use ExpressJS with the right middleware for payload and Request to execute the request against Hitch. Here is a simple

var express = require('express');
var bodyParser = require('body-parser');
var request = require('request-promise');

var application = express();
application.use(bodyParser.urlencoded({ extended: false }));

function buildResponseMessage(content) {
  return ‘A response message’;
}

application.post('/commands/hitch', function (req, res) {
  var options = {
    method: 'GET',
    uri: `https://www.hitchhq.com/${req.body.text}.json`,
    json: true
  };

  request(options)
    .then(function (body) {
    // Received the response from Hitch
    })
    .catch(function (err) {
    (...)
    });
});

application.listen(3001, function () {
  console.log('Server listening on port 3001!');
});

We have the skeleton of our application and we receive the request from Slack. We use the content of the text property to get the name of the API to query Hitch.

Responding to the command

The remaining part consists of answering to Slack with a very simple text message at the beginning. Let’s use the send method of the Express response object.

function buildResponseMessage(content) {
  return ‘A response message’;
}

application.post('/commands/hitch', function (req, res) {
  var options = {
    method: 'GET',
    uri: `https://www.hitchhq.com/${req.body.text}.json`,
    json: true
  };

  request(options)
  .then(function (body) {
    res.send(buildResponseMessage(body));
  })
  .catch(function (err) {
    (...)
  });
});

In the Slack UI, we will see the result after having executed the command /hitch slack.

That’s a good beginning but we can obviously do better!

Instead of returning a text message, we will leverage an object following the principles described in the section “Improving content of messages”. Since Hitch will return a list of changes, we will use attachments in the message.

It remains to create the message to send back to Slack within the buildResponseMessage function. We will specify a text with the API name and map all changes to a Slack attachment.

The attachment will describe the change. The title will be the change name and the author the API provider. Fields can be also specified to identify the kind of changes (create or update). The attachment color can be adapted accordingly. The footer specifies that such hints come from Hitch.

Let’s refactor now the buildResponseMessage function:

function buildResponseMessage(content) {
  let api = content.api;
  let changes = content.changes;

  return {
	text: `*Changes for API ${firstUpperCase(api.name)}*`,
	attachments: changes.slice(0, 3).map(change => {
      return {
        "author_name": firstUpperCase(api.name),
        "author_link": api.website,
        "color": getAttachmentColor(change),
        "author_icon": "https://www.apichangelog.com//static/img/logos/slack-128.png",
        "title": change.title,
        "title_link": change['diff_url'],
        "fields": getFields(change),
        "footer": "from Hitch",
        "footer_icon": "https://c.hitchhq.net/-/images/logo.png"
      };
    })
  };
}

The buildResponseMessage function relies on the three utility functions:

function firstUpperCase(s) {
  return s.charAt(0).toUpperCase() + s.slice(1);
}

function getAttachmentColor(change) {
  return (change.type === 'Update') ? 'warning' : 'good';
}

function getFields(change) {
  let fields = [];
  fields.push({
	"title": "Type",
	"value": change.type,
	"short": true
  });

  if (change.type === ‘Update’) {
    fields.push({
      "title": "Percent",
      "value": change['diff_percent'],
      "short": true
    });
  }

  return fields;
}

Let’s execute again our /hitch command and see the result.

Much better! We could also get hints for the Github API:

Conclusion

In this guide, we introduced the Slack API and how to make this tool interact with external applications to send and receive messages.

The Slack API is wider and also supports the ability to be notified of Slack events through the Real Time Messaging API (Websockets) or the Events API (Web hooks). Such features are outside the scope of this guide and will be described in a next one.