Building a Slack App: Incoming Webhooks

Like many tech companies, DevMynd uses Slack for collaboration and communication. Slack has a number of third-party apps that integrate various services such as GitHub, Twitter, and IFTTT. Third-party apps for Slack make it different than an email or chat client providing contextual messages. However, there are features that are more specific to your team that third-party apps do not provide.

There are a few ways to display custom contextual messages to your team through your own Slack app integration. We’ll explore one of the features available from the Slack API: Incoming Webhooks. We’ll go over Slash Commands and Bots in the future.

What Capabilities Does Slack Have?

Incoming Webhooks: This allows your Slack app to accept an HTTP request (POST) with a customizable message via JSON payload for a specific channel. We currently use this feature by sending a reminder and Google Hangout link for our standup on work from home days.

Slash Commands: This allows the user to interact with your Slack app through a custom command. When a user enters the slash command /command, Slack will send an HTTP request (POST) to your Slack app server where you can customize the response. We currently use this feature to display the daily soup from a local market nearby, when someone types /soup.

Bots: This allows you to create custom conversational interactions between the user and your code. Bots can perform functions similar to a regular user such as posting a message, reacting to an event like joining a channel, etc. They can be integrated with AI platforms.

Helpful Information Around Setup

There are plenty of tutorials around that will help you setup a Slack app. Here are some quick things to know when creating, developing, and testing your app:

  • Once a Slack app is created, it starts out without any features to turn on.
  • A Slack app starts out uninstalled. The app needs to be installed to a team to be used/tested.
  • An incoming webhook feature will give you a distinct URL for you to POST to. You should keep this a secret (save it as an environment variable).
  • A slash command will require an endpoint for it to send data to. It will also pass along a verification token so your server will know that it came from your app from your Slack app settings (Your app > Basic Information > App Credentials).
  • Use ngrok for local development and testing. This will create a tunnel to your localhost/server to the internet.

Creating an Incoming Webhook

Webhooks are great for displaying contextual messages through your app when you want to react/pass messages from an outside service to your users. As mentioned above, here at DevMynd, we use the incoming webhook to post a link to our standup on Wednesdays. We’ll go over how we can accomplish this in a couple of ways.

Method 1: Node/Express Server

Creating an incoming webhook is pretty simple and can be very customizable. The primary functionality you need is a way to make a POST request (we’ll be using request-promise-native).

The first thing we’ll want to do is create a basic message (JSON format) and fill in the contents of it. The Message API has plenty of information about the structure of a message and how to format it. Below, the Slack message resides in a static function called message, which returns an object with a text property.

Once we have our message, we can setup the request by creating a static function which returns our options object for the request. We specify the method as a POST, uri with the Slack internal webhook URL, body of the request with our message object, and we make sure that the request header is specified as JSON.

We can initiate the request by creating another static function which calls request-promise-native (assigned to the rp constant) with our options. We can then export this class to any part of our Node application.

const rp = require('request-promise-native');

class Standup {
  static message() {
    return {
      text: 'Time for standup <!here>: <https://www.devmynd.com/>'
    };
  }

  static options() {
    return {
      method: 'POST',
      uri: 'https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX',
      body: this.message(),
      json: true,
      headers: {
        'content-type': 'application/json'
      }
    };
  }

  static postMessage() {
    const options = this.options();
    const onSuccess = (res) => {
      console.log('Hey you posted a message to the channel!');
    };
    const onError = (err) => {
      console.error('Uh oh, something broke...', err);
    };

    rp(options)
      .then(onSuccess)
      .catch(onError);
  }
}

module.exports = Standup;

An example of being able to trigger the internal webhook from a URL endpoint on your server (http://localhost:3000/standup), we could do something like so in our express server file:

const express = require('express');
const Standup = require('./standup');
const port = 3000;


const app = express();

app.get('/standup', (req, res) => {
  Standup.postMessage();
  res.status(200).end()
});
app.listen(port, (error) => {
  if (error) {
    console.error('Unable to listen for connection', error);
    process.exit(10);
  }
  console.info(`Express server is listening on port ${port} in %s mode`, app.settings.env);
});

Once you have the basic functionality of the webhook, you can call the Standup function any number of ways. We’ll use a Cron scheduler (CronJob) to call the Stand.postMessage function at the designated time we want. This can be kicked off when the server starts.

const CronJob = require('cron').CronJob;
const Standup = require('./standup');

const standupCron = () => {
  new CronJob({
    cronTime: '00 00 12 * * 3',
    onTick: Standup.postMessage,
    onComplete: null,
    start: true,
    timeZone: 'America/Chicago'
  });
};

module.exports = standupCron;

Method 2: Heroku Scheduler Task

If you plan on hosting your Slackbot on Heroku, you can use the Heroku Scheduler add-on to trigger a curl request at a specific time. An example of the same message:

curl -X POST -H 'Content-type: application/json' \
--data '{"text":"Time for standup <!here>: <https://www.devmynd.com/>"}' \
https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXXXXXX

You can use Heroku Scheduler to trigger the curl above in intervals. This probably the most simple way to trigger your internal webhook.

While the curl via Heroku Scheduler Task is quick and simple, it is less robust in terms of when you use the webhook. Some things to be aware of with timing is that if you are using the free Heroku dyno, your dyno may go to sleep and take time to wake up.

That wraps up the quick intro to Internal Webhooks. Come back soon for the second part of this blog post, which will cover writing a Slack app Slash Command!

DevMynd is innovation firm in Chicago and San Francisco with practice areas in custom software development, mobile and web application development.

Sonny is a member of DevMynd’s software engineering team focusing on mobile apps and web development. He has been with the company since 2016.