Blog / Integrations

Using Slack Webhooks with Kentico Cloud


by Bryan Soltis

Oct 11, 2017

Kentico Cloud’s webhook support is a great tool for developers to use within their applications. By configuring this feature, you can easily automate processes and simplify your custom code. Webhooks can update external systems, ensure data integrity, and keep all your systems (and people) in sync as content changes. I’ll show you how you can use this new capability to update your team’s Slack channels when content is published. 

Integrations make everyone’s lives easier. From developers to marketers, anyone can benefit from a little automation in their daily routine. Recently, we announced webhook support in Kentico Cloud. This new capability allows you to integrate your content updates into every aspect of your business. This means less time sitting in the dark when it comes to content changes and more time playing Minesweeper (ah, 1996…). 

A big part of keeping teams updated is communication. More and more, companies are relying on instant messaging to inform team members of updates, meeting requests, and that there is cake in the breakroom. With Slack being one of the most popular clients, I wanted to show you how you can leverage Kentico Cloud’s webhook support with your team’s communication platform. I published a similar article for Kentico EMS, which I will refer to a few times in this one.

Let me show you how…

Defining the Process

The first step in the process is to define what your integration looks like. Because every company and project is different, how you connect your systems all depends on your requirements. In my case, I wanted to simply post a message to a channel when a Kentico Cloud content item was published and unpublished. This process would help my marketers stay up to date on new content. This could be especially helpful if you have several external editors who are creating and publishing content within your project. 

Here is a workflow for my functionality.

Creating the Webhook

With the workflow defined, I was ready to create my integration. With Webhooks, I just needed a function that was available for Kentico Cloud to call when content was updated. This could have been a route/function within my MVC application, an Amazon Lambda function, or (as it tends to be in my case) an Azure Function. Because Azure Functions have configuration specific for this need, it was a good fit for my solution. 

In Azure Functions, I created a new Generic Webhook function.


For the configuration, I left the defaults, as I only needed simple HTTP support. 


I also elected to leave the Generic JSON type selected. Azure Functions do offer support for Slack-specific functions, which simplify your calls. Because I was leveraging integration code from my previous article, I chose to use a generic call instead.

You can read more about Azure Function Slack Webhook support here.

Configuring Kentico Cloud

The next step of the process was to configure webhooks within Kentico Cloud. In my project, I created a new webhook, using my Azure Function URL.


Note that the secret value will be used later in the process.

Adding the Kentico Cloud Integration

With the function created, I was ready to add my code. I have documented the process a few times in other blogs, so here is an overview of what I did.

  • Created a project.json
  • Added a reference to KenticoCloud.Delivery API
  • Added KC Project ID / Preview API Key to run.csx 

With the base function in place, I was ready to port over my Slack integration. Because Azure Functions supports additional files, I uploaded my existing Slack code files, changing the extension to .csx.  These were files I used in my previous Slack integration. 

payload.csx
This file is the model for the Slack message.

using Newtonsoft.Json;
public class Payload
{
    [JsonProperty("channel")]
    public string Channel { get; set; }

    [JsonProperty("username")]
    public string Username { get; set; }

    [JsonProperty("text")]
    public string Text { get; set; }
}


slackclient.csx
This file contains the Post function for posting messages to Slack.

using System.Text;
using System.Net;
using Newtonsoft.Json;

using System.Collections.Specialized;

//A simple C# class to post messages to a Slack channel
//Note: This class uses the Newtonsoft Json.NET serializer available via NuGet
public class SlackClient
{
    private readonly Uri _uri;
    private readonly Encoding _encoding = new UTF8Encoding();

    public SlackClient(string urlWithAccessToken)
    {
        _uri = new Uri(urlWithAccessToken);
    }

    //Post a message using simple strings
    public void PostMessage(string text, string username = null, string channel = null)
    {
        Payload payload = new Payload()
        {
            Channel = channel,
            Username = username,
            Text = text
        };

        PostMessage(payload);
    }

    //Post a message using a Payload object
    public void PostMessage(Payload payload)
    {
        string payloadJson = JsonConvert.SerializeObject(payload);

        using (WebClient client = new WebClient())
        {
            NameValueCollection data = new NameValueCollection();
            data["payload"] = payloadJson;

            var response = client.UploadValues(_uri, "POST", data);

            //The response text is usually "ok"
            string responseText = _encoding.GetString(response);
        }
    }
}


With the Slack files in place, I was ready to update my function. First, I added code to validate the call, using the Kentico Cloud secret value.

    // Get the signature for validation
    IEnumerable<string> headerValues = req.Headers.GetValues("X-KC-Signature");
    var sig = headerValues.FirstOrDefault();

    // Get the content
    var content = req.Content;
    string jsonContent = content.ReadAsStringAsync().Result;

    // Generate a hash using the content and the webhook secret
    var hash = GenerateHash(jsonContent, strWebhookSecret);

    // Verify the notification is valid
    if(sig != hash)
    {
        log.Info("Unauthorized attempt!");
        log.Info(sig);
        log.Info(hash);
        return req.CreateResponse(HttpStatusCode.Unauthorized, new
        {
            error = "Unauthorized!"
        });
    }
…


Once the call was validated, I created a list of all the updated items, and the status of the update.

    dynamic data = JsonConvert.DeserializeObject(jsonContent, settings);

    if (data == null)
    {
        return req.CreateResponse(HttpStatusCode.BadRequest, new
        {
            error = "Please pass data properties in the input object"
        });
    }

    // Determine the operation
    // Only process if it is publish or unpublish
    string strOperation = data.message.operation.ToString().ToLower();
    switch(strOperation)
    {
        case "publish":
            blnValid = true;
            blnPublish = true;
            break;
        case "unpublish":
            blnValid = true;
            blnPublish = false;
            break;
    }

    // Make sure it's a valid operation
    if(blnValid)
    {
        List<string> lstCodeNames = new List<string>();
  
        foreach(var item in data.data.items)
        {
            lstCodeNames.Add(item.codename.ToString());
        }


The next step was to post the update to Slack. To do this, I looped through each of the updated items, retrieved their details from Kentico Cloud, and posted a message to the specified Slack channel.

       ...
       // Post to Slack
        if(lstCodeNames.Count > 0)
        {
            await PostToSlack(lstCodeNames, log);
        }
       ...

private static async Task PostToSlack(List<string> list, TraceWriter log)
{
    try
    {

        SlackClient slackclient = new SlackClient(strSlackURL);
        DeliveryClient deliveryclient = new DeliveryClient(strKenticoCloudProjectID, strKenticoCloudPreviewAPIKey);
            
        foreach(string codename in list)
        {
                
             // Get the details from Kentico Cloud
            DeliveryItemResponse response = await deliveryclient.GetItemAsync(codename);
            if(response != null)
            {
                var item = response.Item;         
                log.Info(item.GetString("name"));

                slackclient.PostMessage(username: strSlackUsername,
                    text: "Content update:" + item.GetString("name") + (blnPublish ? " PUBLISHED!" : " UNPUBLISHED!"),
                    channel: strSlackChannel);
            }
            else
            {
             log.Info($"Item not found!");
           }
        }
    }
    catch (Exception ex)
    {
        log.Info(ex.Message.ToString()); 
    }
}

Testing

With the configuration and function in place, I was ready to test. I opened Kentico Cloud and updated a content item. To start, I PUBLISHED a new content item.


I then verified the message was posted to the Slack channel.


I then UNPUBLISHED the item.


Lastly, I verified the new message was posted correctly.

Moving Forward

In this blog, I showed you how easily you can integrate your company’s Slack channel into your content management process. This functionality can help your team be more productive by delivering updates in real-time. This can let you know of issues sooner, and cut down on your custom development. I hope it helps you make some great solutions and gets your team working smoothly. Now, about that cake in the breakroom….

Get the code