Integrating ServiceNow with Slack

Slack + ServiceNow One of our teams at work recently started using Slack for all their team communication and approached me about possibly having ServiceNow shoot Incident assignment notifications into one of their Slack channels. As it turns out, integrating ServiceNow and Slack is really easy. In this article I’ll show you how to enable the necessary Slack “Incoming Webhooks” integration service as well as give you a ready-to-go ServiceNow Script Include you can use to post messages to Slack from any scripted ServiceNow Business Rule. Finally, I include some examples and ideas for how to use and re-use the Script Include in your own Business Rules.

Getting access to the Slack API

To get started, you’ll need an endpoint URL for the Slack API to which ServiceNow can send web service calls. To get this URL, from within any Slack channel (either in the Slack app or the Slack website), simply drop down the arrow next to the channel name and select “Add a service integration”. It doesn’t matter which channel you do this in—I’ll show below how you can use this same integration endpoint to post to any channel within the same team.

Slack “Add a service integration…”

On the next screen, scroll almost all the way to the bottom and click “+Add” next to “Incoming Webhooks”.

Slack Choose Incoming Webhooks

Lastly, click “Add Incoming Webhooks Integration”.

Slack Incoming Webhooks setup

On the next screen you should see a long web address labeled “Webhook URL”, similar to this:

https://hooks.slack.com/services/xxxxxxxxx/xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx

Save this URL someplace safe. I’ll show you where to use it in a bit. Slack doesn’t require any kind of authentication to use this API, so the tokens in this URL essentially are your password. Keep it a secret.

Creating the REST Message in ServiceNow

The first step in ServiceNow is to set up a REST Message. If you’re running Fuji you can actually skip this step and go straight to the Script Include, as the new RESTMessageV2 API in Fuji doesn’t require a REST Message database record. If you’re still on Eureka or want to keep the integration backwards-compatible with earlier releases for any reason, read on.

  1. Navigate to System Web ServicesOutboundREST Messages in your ServiceNow instance.
  2. Click “New” to create a new REST Message.
    • Type “Slack” for the name.
    • Click the lock icon to unlock the Endpoint field and type “${endpoint}” there.
    • Type something useful in the Description field if you go for that sort of thing.
  3. Right-click in the header and choose “Save” to submit the new record and stay where you are.
  4. At the bottom of the newly-saved record, there will be a related list containing REST Message Functions, of which there should be four: “get”, “post”, “put”, and “delete”. Check the box to the left of all these but “post” and use the drop-down at the bottom to delete them. The only one we need to keep here is the “post” function.
  5. Click into the “post” function.
    • Verify the Endpoint here is also set to “${endpoint}” (it should’ve inherited this setting from the parent REST Message).
    • In the Content field, type “${payload}”.
  6. Click “Update”.

That’s it, the REST Message and REST Message Function should be ready to go.

The Script Include

To create the Script Include, follow these steps:

  1. Navigate to System DefinitionScript Includes in your ServiceNow instance.
  2. Click “New”.
    • Name the Script Include “SlackMessage”.
    • Give it a description if you want.
    • Copy and paste the script below.
  3. Click “Submit”.

Here’s the script itself:

var SlackMessage = Class.create();

SlackMessage.prototype = {
    'initialize': function() {
        if (gs.getProperty('slack_message.default_icon_url') != '') {
            this.payload.icon_url = gs.getProperty('slack_message.default_icon_url');
        }
        else if (gs.getProperty('slack_message.default_icon_emoji') != '') {
            this.payload.icon_emoji = gs.getProperty('slack_message.default_icon_emoji');
        }
    },
    
    'send': function (text, channel) {
        // Set the text and channel (or fall back to defaults)
        this.payload.text = text || this.payload.text;
        this.payload.channel = channel || this.payload.channel;
        
        // Encode the payload as JSON
        var SNJSON = JSON; // Workaround for JSLint warning about using JSON as a constructor
        var myjson = new SNJSON();
        var encoded_payload = myjson.encode(this.payload);
        
        // Create and send the REST Message
        var msg = new RESTMessage('Slack', this.method);
        msg.setStringParameter('endpoint', this.endpoint);
        msg.setXMLParameter('payload', encoded_payload);
        var res = msg.execute();
        return res;
    },
    
    'endpoint': gs.getProperty('slack_message.default_endpoint'),
    'method': 'post',
    'payload': {
        'channel': gs.getProperty('slack_message.default_channel'),
        'username': gs.getProperty('slack_message.default_username'),
        'text': '',
        'attachments': []
    },
    
    'type': 'SlackMessage'
};

Now, I said earlier if you’re running Fuji you could skip the step for adding the REST Message. If you did that, you’ll want to find the following block of code in the above script . . .

// Create and send the REST Message
var msg = new RESTMessage('Slack', this.method);
msg.setStringParameter('endpoint', this.endpoint);
msg.setXMLParameter('payload', encoded_payload);
var res = msg.execute();
return res;

. . . And replace it with this:

// Create and send the REST Message
var msg = new sn_ws.RESTMessageV2();
msg.setEndpoint(this.endpoint);
msg.setHttpMethod(this.method);
msg.setRequestBody(encoded_payload);
var res = msg.execute();
return res;

This alternative code instantiates a new RESTMessageV2 object (instead of the older RESTMessage object) and does so without specifying any parameters. One of the neat things about the new version of the Outbound REST API is you don’t have to specify an existing REST Message, but can use the setter methods you see here to set the Endpoint and the HTTP Method, as well as directly set the Content Body of the message, all at runtime. Obviously if there’s a lot of setup (special headers and authentication and such) you may still want to go the former route of declaring the REST Message up front and calling it here, but since the Slack API doesn’t require any of that, being able to do it this way keeps things simple.

System Properties

You may have noticed the Script Include pulls in a handful of System Properties. I like using System Properties because they allow you to change settings on the fly later without re-deploying code. You can get a list view of all System Properties by typing “sys_properties.list” in the search box at the top of the left sidebar in ServiceNow. To create a new System Property from there, simply click the “New” button.

You’ll need to declare the following System Properties in your instance for the Script Include above to function properly:

Name Value
slack_message.default_endpoint Here’s where you should paste that Slack Webhook URL you put in a safe place way back in the first section above.
slack_message.default_channel Specify the channel to which you’ll post the majority of your Slack messages, with a leading hash symbol, such as “#general” or “#servicenow”. The Slack API also allows specifying a person’s username preceded by an at symbol to send direct messages instead, such as [email protected]
slack_message.default_username This is the display name of the bot that will post the messages. I set mine to “ServiceNow”, but this could be anything you like.
slack_message.default_icon_url Specify a URL of an image to be used as the avatar for your bot, or leave this blank if you want to use an Emoji instead (see the next property). This image can be anywhere on the web or hosted on Slack as long as you’ve shared it to the whole team and not left it private.
slack_message.default_icon_emoji Specify an Emoji code including surrounding colons, such as “:warning:”. Slack uses the same Emoji codes as Campfire and Github, for which there is a very handy Emoji cheat sheet. This setting will be ignored if you’ve specified an image URL instead (see the previous property).

I called these all defaults since, as I’ll show below, you can override any or all of them in your individual Business Rules. As a best practice I recommend you rename these to include a company prefix, e.g. acme.slack_message.default_endpoint. If you do, just don’t forget to modify all the gs.getProperty() calls in the Script Include to match the new names.

Examples

Here’s how easy it is to send a message into the default channel using this Script Include. In an advanced Business Rule (with conditions specified however you like), just call the send() method on a new SlackMessage object:

var slack = new SlackMessage();
slack.send('Hello World!');

To send the message to a different channel or as a direct message to an individual, simply specify the channel or user in a second parameter of the send() method:

var slack = new SlackMessage();
slack.send('Hello World!', '#random');
var slack = new SlackMessage();
slack.send('Hello World!', [email protected]');

You can get even more fancy using the payload member variable to override defaults and/or specify additional API options (this is very similar to the Business Rule script I actually wrote for the team I mentioned in the opening paragraph):

// Initialize a new SlackMessage
var slack = new SlackMessage();

// Set up the payload
slack.payload.text = 'An Incident has been assigned to ' + current.assignment_group.name + ':';
slack.payload.icon_emoji = ':exclamation:';
slack.payload.attachments.push({
    'title': current.number.toString(),
    'title_link': 'https://' + gs.getProperty('instance_name') + '.service-now.com/nav_to.do?uri=incident.do?sys_id=' + current.sys_id,
    'text': current.short_description.toString()
});

// Fire off the message
slack.send();

The above will result in a Slack message looking like this:

Slack Message

Don’t miss how you can call the send() method without any parameters as long as you’ve specified useful values in the payload beforehand, and that you may need to use toString() when you set values if you’re not using string concatenation or something else that would implicitly cast the contents of a record field into a string.

There are more options I haven’t gone over here and I encourage you to read up on the Slack Incoming Webhooks API for more details. The important thing to notice is that “payload” is a JavaScript object that gets encoded as JSON and sent in as the Request Body of the REST message, so you can declare any option you want from the Incoming Webhooks API as a key/value pair in that payload object and it should work.

I should mention that you can even override the default endpoint if you need to send a message to an entirely different team:

var slack = new SlackMessage();
slack.endpoint = 'https://hooks.slack.com/services/xxxxxxxxx/xxxxxxxxx/xxxxxxxxxxxxxxxxxxxxxxxx';
slack.send('Hello World!', '#general');

Lastly, the send() method returns the server response. If you store that response you can debug by grabbing values out of it as shown below:

// Send the message
var slack = new SlackMessage();
var response = slack.send('Hello World!');

// Show error message if it failed
if(response.getStatusCode() != 200) {
    gs.addInfoMessage("response.getBody: " + response.getBody());
    gs.addInfoMessage("response.getStatusCode: " + response.getStatusCode());
    gs.addInfoMessage("response.getErrorMessage: " + response.getErrorMessage());
}

Ideas

What sorts of things can you do with this? The sky’s the limit really. What I was tasked with doing was simply send a message into a specific channel whenever an Incident was assigned to a specific User Group. By specifying different conditions in your Business Rule, you could send messages only if an Incident is high priority or attached to certain Configuration Items.

One idea I thought of is adding a Slack username field to the sys_user table and then using that to push a private message whenever a user has a ticket assigned to them, something like:

if (current.assigned_to.u_slack_username != '') {
    slack.send('Ticket ' + current.number + ' has been assigned to you!', '@' + current.assigned_to.u_slack_username);
}

If you use this Script Include for something interesting, I’d love if you’d leave a comment below or drop me a line on social media. Cheers! 

  • Pingback: EP40: A delightfully uplifting episode | Hacking Business Tech()

  • Raghu Manchiraju

    Hi Joey – nice article. This will serve as a starting point for slack and service now integration. Do you have any sample for outbound web hook to service now.

    • What sort of thing do you have in mind for this? Trigger words in Slack for creating and/or updating records in ServiceNow? I can try to work up an example if you have a specific use case.

      • Raghu Manchiraju

        Joey thanks for quick reply. I am looking for both. Creating service now incident or service request. Also checking the status of the incidents or service requests as well.

        • Oh, checking status makes sense, too. All right, lemme see what I can mock up. I don’t know how soon I’ll have time to work on it, but it sounds fun. Hopefully I’ll have something soon.

          • zberke

            @@Joey Day:disqus@ or @Raghu Manchiraju:disqus were you able to confirm that an outbound web hook to servicenow from slack was possible?

          • David Tsai

            I’m curious about this, too! @joeyday:disqus or @raghumanchiraju:disqus did you have any success building an outbound web hook from Slack to Service-Now? Trigger phrases for creating new Incidents would be amazing!

  • Jigar

    I’m trying to delete user account matching e-mail. If we submit offboard in SN the Slack activity should look for the user’s email and delete their slack account. Can anyone help?

  • Jigar

    can anyone help with it?

    • Jigar, on a cursory glance I’m not seeing anything in the Slack API that would allow you to delete a Slack account via a web service call from somewhere else like ServiceNow. So it seems what you want to do may not be possible to automate. You may just need to build this into your process as a manual step for an admin to go into Slack and delete or deactivate the account. Sorry. :-(

      • Jigar

        There is a way to delete user but my problem is not that.

        My problem is that I’m able to get user list
        (https://api.slack.com/methods/users.list
        but I need to search user with email address and get user id:
        Once we have user id we can send command to delete

        I need a way to search that user id using email.

        • What if you create a custom field in ServiceNow to track the user’s ID, then query Slack once for all users to populate that field? Then you would just know the Slack ID for all your users on the ServiceNow side and could send the command to delete them when needed without having to first query to find out their ID.

          Out of curiosity though, what command are you sending to Slack to delete an account? I took a second look at the API documentation and I’m still not seeing that. o_O

          • Jigar

            When user logins in with Okta ServiceNow is involved so we don’t have user id. we are using DELETE /User/{id}.

  • Brian

    Joey,
    This was so helpful, thanks for posting. I set it up to send changes from ServiceNow to Slack on state change when they are approved to implement. Although I could log into SN and check all of the changes, I always have Slack open and can just check there to see what’s coming up or recent changes.

    Thanks again.

  • Smart Ant

    Hello Joey,
    Thank you so much for this very helpful article.
    I have a follow up question though. How can I configure so that SNOW Incident messages goes to one group in Slack and Change messages to another group?
    Appreciate your help in advance.
    Thanks,
    Kiran

    • Barry

      You can add the slack.endpopint url directly in the business rule. So when that rule is fired it will send to the specific endpoint you list.

      var slack = new SlackMessage();
      slack.endpoint = ‘https://hooks.slack.com/services/xxxxxxxx/xxxxxxxxxxxxxxx/xxxxxxxxx

  • Pingback: Why I – Stacie Arellano()

  • Leslie Know

    Hi Joey thank you for this very helpful tutorial. I have a question, we want to be able to ping individual users when an incident is assigned to a user. We need to be able to do this without storing the Slack usernames in ServiceNow, though. Is there a way to dynamically query Slack to get the Slack username based on the email address stored in the ServiceNow user record?

    https://api.slack.com/methods/users.list

    • Oh man, I guess you could hit Slack every single time, but it would reduce so much overhead if you cached the Slack usernames somewhere. Just hit Slack once to get the username the first time you need a notification to go out, then cache it either right on sys_user or in a separate table. I wasn’t aware Slack had a way to query for usernames, so that’s a good find. Using this does save you the trouble of front-loading the usernames manually.

  • Abe Froman

    For screenshots, Command – Shift – 4 then space bar turns your cursor into a camera and on click takes a picture of the window your cursor is over, cropping it perfectly.