funcy — a Serverless Slack App using Fn
I love the extensibility that Slack provides via Apps — it’s a rich ecosystem consisting of Bots, (Incoming) Webhooks, Slash Commands, etc. One such app is the awesome Giphy for Slack, which is something I enjoy using on a daily basis.
This post demonstrates how to develop a Serverless Fn function as a Webhook, that serves as an API for a trimmed down version of the Giphy app for Slack.
You can also check out the following blog posts which also covers interesting Webhook-related use cases
The original Giphy app returns a bunch of GIFs for a search term and the user can pick one of them (notice the Shuffle option below)
funcy tweaks it a bit by simply returning a (single) random image for a search keyword using the Giphy Random API — for your viewing pleasure only!
Sounds interesting? Let’s dive in…
Overview
funcy is built as a Slash Command within Slack. As a user, you can invoke it from within your workspace using /funcy <your search term>
. Once you deploy the Fn function (as per steps outlined in subsequent section), all you need to do is create a Slack app and configure a Slash Command which points to the function’s invoke endpoint.
The function has the logic to invoke the GIPHY Random API and will get invoked when the /funcy
command is triggered by the user from the workspace.
Here is a diagram demonstrating the flow
The code is available on GitHub, but let’s walk through it real quick.
Code…
The serverless function is written in Go — its entry point is via main
which delegates to the funcy
function.
Safety first
We start off by verifying that the request is indeed from Slack by using the Signing Secret of our Slack App. The details are in the Slack documentation — here is how it’s implemented in our function using Go.
- We get the signing secret from our function application configuration (
SLACK_SIGNING_SECRET
) - Value of the
X-Slack-Request-Timestamp
header from the HTTP request - The HTTP payload (
application/x-www-form-urlencoded
data sent by Slack)
The above information is used to match the signature sent by Slack in the form of X-Slack-Signature
header in the HTTP request — this is the signature created by combining the signing secret with the body of the request using a standard HMAC-SHA256 keyed hash.
We proceed further only if the signatures match:
Extracting the Search Term
The funcy
Slack Slash command works on the basis of the search term/keyword entered by the user. This is done by first parsing the application/x-www-form-urlencoded
data sent by Slack and then reading the value of the text
attribute
Here is an example of the data sent by the Slack Slash command when it is invoked:
The parsing logic was taken from the Go standard library:
Invoking GIPHY (Random) API
Once we have the search term, we invoke the GIPHY Random API and the response is unmarshaled into the GiphyResponse
struct — this consists of information related to the (random) GIF. For the scope of this function, we just care about the title and the GIF URL (downsized version).
Responding to Slack
If all goes well, we construct a JSON response and send an HTTP 200 response back to Slack as per guidelines — the response contains the GIF title and its URL. The GIF is directly rendered to the caller in the Slack workspace window:
Please note that Slack expects an HTTP 200 response within 3 seconds — the function is able to adhere to this SLA
You need to take care of a few things before you deploy and have fun with funcy.
Pre-requisites
Fn setup
Download (or update) the Fn CLI
curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
Please ensure that Fn server can be accessed from the public internet (e.g. install it on a public VM), or use ngrok on your local machine
fn start
Switch to the correct context e.g. to use the default
context which gets installed out of the box
fn use context default
Slack workspace (sign-in or create one)
Sign into your Slack workspace — if you don’t have one, please create it
Giphy API key
Adapted from the Giphy documentation
Start by creating a GIHPY account (it’s free!) and create an app. Each application you create will have it’s own API Key.
Please note down your API key as you will be using it later
Configuration and Deployment
Create application and deploy the function
Create the Fn application using the command below:
fn create app funcy
Deploy the function to the application
fn -v deploy --app funcy --local
Get invoke endpoint — this will be used as a configuration input to the Slash Command
fn inspect function funcy fun --endpoint//e.g. http://localhost:8080/invoke/01D60BXYFRNG8G00GZJ0000002
Configure Slack
These steps have been adapted from the Slack documentation
Create a Slack App
Before you start, you’ll need a Slack App — click here to get started
Create a Slash Command
Once you’re done creating the app, head to your app’s settings page, and then click the Slash Commands feature in the navigation menu.
You’ll be presented with a button marked Create New Command, and when you click on it, you’ll see a screen where you’ll be asked to define your new Slash Command with the required information:
Enter the following info — make sure to enter your function invoke endpoint for Request URL e.g. http://[host-ip]:[port]/invoke/[your-function-id]
Install app to workspace
Once you’re done creating the Slash Command, head to your app’s settings page, click the Basic Information feature in the navigation menu, choose Install your app to your workspace and click Install App to Workspace — this will install the app to your Slack workspace to test your app and generate the tokens you need to interact with the Slack API.
Make a note of your app Signing Secret
As soon as you finish installing the app, the App Credentials will show up on the same page. You need to grab your Slack Signing Secret from there
You’re almost there! You just need to update your application.
Update application
Update the (function) app to set Slack Signing Secret and GIPHY API key in the configuration
fn update app funcy --config SLACK_SIGNING_SECRET=<fill-in-secret> --config GIPHY_API_KEY=<fill-in-apikey>
fun(cy) time!
From your workspace, invoke the command using /funcy <search term>
e.g. /funcy serverless
or maybe another cat using /funcy cat
Thanks for reading ! I hope this was fun and you learned something along the way as well. Please drop in a comment if you have questions, feedback or just want to say hello. :-)