When you really start to get into web stack/cloud solutions, you’ll find pretty quickly that having multiple pieces of software talk to each other gets a lot easier. No more worrying about network visibility, opening ports in firewalls, etc. Generally speaking, you talk JSON to an API. Put some information here. Get some information there. Automate.
Recently as a test, I decided to use iconik’s Webhooks to automate sending notifications to a Slack channel. I thought “what might be useful of a team to get updates on?”, so I decided to build a very simply action that posts any time a review and approve status is modified in iconik back into a Slack channel. And I did it all without needing a single VM, computer, or dev environment. This is how.
First and foremost, it is probably helpful to understand what a Webhook is. Cantemo’s iconik system allows for using these tools to help aid in automation, and their function is really quite simple. When something happens in iconik, we can subscribe to that something and deliver a notification to ourselves. Deliver what to where, you might ask? It is actually quite simple – the Webhook delivers a JSON payload as a POST to a URL. Simple as that.
Well I don’t have a web server on the internet? What do I do?
The good news here is that you don’t necessarily have to. You just need some place that can process that payload quickly and then communicate with other systems that speak the same language. Enter serverless architecture. For this example, we will be using Google’s Cloud Functions, but we could just as easily use AWS Lambda or Microsoft Azure Functions. The beauty of these things is that, unless your volume is VERY high, they are almost negligible in price to run.
Ok, so I can listen for webhooks now. But then what?
Well we need to send them back to Slack, so we hop over to Slack’s API and build an incoming Webhook there. The process is pretty well spelled out in the documentation, but here are the Cliffs Notes.
- Hit the URL above
- Log in, create a new Slack API app
- For that app, create a new incoming Webhook to post to your channel
- Save that URL for later
So I just have iconik send a Webhook to Slack? Is it really that easy?
No, sadly. But it isn’t too difficult. When a Webhook is sent from iconik, it comes formatted in a specific way and contains all the information you might need about the item being worked on, and if any information it doesn’t contain is needed, using the iconik API can provide it. But what we receive from iconik is iconik JSON.
Slack, on the other hand, also expects its own language to be sent. It is still JSON, but formatted completely differently to iconik’s.
So what do I need to do?
So lets take a look at what we get from both systems. First lets look at a typical iconik response. For my example, I’m creating a Webhook on assets -> All -> approval_status. That means that any time approval status is modified for any asset in iconik, it triggers my Webhook. And it sends something that looks like this:
{
"data": {
"change_date": "2019-02-20T01:50:27.509023+00:00",
"externals": [],
"groups": [],
"min_number": 1,
"object_id": "8244e1fe-3475-11e9-898f-0a580a3c0d3f",
"object_type": "assets",
"request_date": "2019-02-20T01:50:27.082000+00:00",
"requested_by": "774ac51c-f6db-11e7-b11a-0a580a3c0323",
"share_id": null,
"status": "REQUESTED",
"users": [
"774ac51c-f6db-11e7-b11a-0a580a3c0323"
]
},
"event_type": "assets",
"object_id": "8244e1fe-3475-11e9-898f-0a580a3c0d3f",
"operation": "create",
"realm": "approval_status",
"system_domain_id": "73775d86-f6d8-11e7-8ff5-0a580a300418"
}
For my Slack message, I just want to let people know what the status is and give them a link to the asset. Now I have a lot more information here, but I’m not going to use anything other than data -> status and data -> object_id. But more on that later.
Slack requires a different format to be sent to it. There is a lot of information on Slack Message Attachments in their documentation. We are going to use a little bit of that to make it prettier than just a line of text. Here is what a properly formatted message to Slack would look like:
{
"attachments": [
{
"fallback": "Approval Status Updated for {object_id}",
"title": "Approval Status Updated for {object_id}",
"title_link": "https://app.iconik.io/asset/{object_id}",
"fields": [
{
"title": "Status",
"value": "{status}",
"short": false
}
]
}
]
}
Yeah, those aren’t the same at all. So now what?
Now we write some simple code. Since I’m using Google Cloud Functions, I’ll walk you through my process. I’ve chosen to use Google’s Python 3.7 function. My code in this case does very little error trapping, so keep that in mind. Log into your Google cloud console, select Cloud Functions, create a new function, and select Python 3.7 as your function language. Also go to the requirements.txt and add requests to the required package. More on that later.
We have two things we need to do in this little function.
- Read our iconik Webhook, get the info we need out of it, and format that data into our Slack message
- Send the Slack message to our Incoming Webhook from earlier. Here is the entire code to do that, we will break it down after.
import json import requests from string import Template slack_incoming_webhook = 'paste your Slack Webhook URL here' def slack_post(request): slack_template = """{ "attachments": [ { "fallback": "Approval Status Updated for $item_id", "title": "Approval Status Updated for $item_id", "title_link": "$item_url", "fields": [ { "title": "Status", "value": "$status", "short": false } ] } ] }""" request_json = request.get_json(force=True) try: status = request_json['data']['status'] except: print("Couldn't find item status") return "Couldn't find item status",400 try: item_url = "https://app.iconik.io/asset/" + request_json['object_id'] except: print("Couldn't create item URL") return "Couldn't create item URL",400 try: item_id = request_json['object_id'] except: print("Couldn't find object ID") return "Couldn't find object ID",400 message = Template(slack_template) data = message.safe_substitute(status=status,item_url=item_url,item_id=item_id) header = {"content-type":"application/json"} r = requests.post(slack_incoming_webhook,data=data,headers=header) return data |
Let’s break this down a bit. It isn’t a very long script, but there are a few important bits.
import json import requests from string import Template |
This is simply importing the libraries we need to use. The json library is used to work with json objects in python. We use requests to POST our message to Slack. Lastly, we use Template to reformat our Slack message.
slack_incoming_webhook = 'paste your Slack Webhook URL here' |
This is simply a variable that stores the value of our Slack Incoming Webhooks URL
def slack_post(request): |
Cloud Functions run by receiving JSON data and delivering that to a function. Here we are simply defining our only function in this script. This is what runs and the (request) object that it works with is the JSON that iconik sends to it.
slack_template = """{ "attachments": [ { "fallback": "Approval Status Updated for $item_id", "title": "Approval Status Updated for $item_id", "title_link": "$item_url", "fields": [ { "title": "Status", "value": "$status", "short": false } ] } ] }""" |
This is our template for our Slack message. We will use this and replace the variables (starting with $) with the actual values we got from iconik
request_json = request.get_json(force=True) try: status = request_json['data']['status'] except: print("Couldn't find item status") return "Couldn't find item status",400 try: item_url = "https://app.iconik.io/asset/" + request_json['object_id'] except: print("Couldn't create item URL") return "Couldn't create item URL",400 try: item_id = request_json['object_id'] except: print("Couldn't find object ID") return "Couldn't find object ID",400 |
The first line here just says “hey, give me the JSON that got passed to the function”. Then the following try statements are checking that JSON for the little bits of info we need and then adding them into variables we will put into our template.
message = Template(slack_template) data = message.safe_substitute(status=status,item_url=item_url,item_id=item_id) |
This runs our transform – so now the message variable contains our actual real message as a Template to be replaced. It is important to know that the message variable is now an object, not a string/ The next line runs safe_substitute which now converts our message object to a string and we called that string variable “data”
header = {"content-type":"application/json"} r = requests.post(slack_incoming_webhook,data=data,headers=header) return data |
Last but not least, we then format our POST and use the requests library to send it to Slack.
If you’ve followed all the steps above, you should now be able to go into your iconik domain, select any asset, and start a review process on it. If you were successful, you will see a message the slack channel your incoming Webhook posts to.