Using AWS Lambda Function URL to build a Serverless backend for Slack
Tags: tutorial, serverless, showdev, programming
Published: 2022-04-28 05:35:18

A combination of AWS Lambda and Amazon API Gateway is a widely-used architecture for serverless microservices and API based solutions. They enable developers to focus on their applications, instead of spending time provisioning and managing servers.

![](https://docs.aws.amazon.com/apigateway/latest/developerguide/images/getting-started-overview.png)

API Gateway is a feature rich offering that includes with support for different API types (HTTP, REST, WebSocket), multiple authentication schemes, API versioning, canary deployments and much more! However, if your requirements are simpler and all you need is an HTTP(S) endpoint for your Lambda function (for example, to serve as a webhook), you can use [Lambda Function URLs](https://docs.aws.amazon.com/lambda/latest/dg/lambda-urls.html)! When you create a function URL, Lambda automatically generates a unique HTTP(S) endpoint that is dedicated for your Lambda function.

This blog post demonstrates how to use Lambda function URL with a practical example. You will build a [Go Lambda function](https://docs.aws.amazon.com/lambda/latest/dg/lambda-golang.html) to serve as a serverless webhook backend for [Slack](https://slack.com/).

It's a step-by-step guide that covers:
- Overview of the application
- Configure and deploy the function (along with some gotchas you need to watch out for!)
- How to configure Slack to enable the end to end integration
- Test the app and have fun!

By the end of this blog, you would have configured, integrated and deployed a useful (and hopefully fun?) app using Lambda function URL. In the process, you will get an overview of this feature that you can utilise when building your own solutions!

> The code is [available on GitHub](https://github.com/abhirockzz/awsome-slack-backend)

## How it works


![](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8kqfu4ki5fcm1evviptl.png)

The sample app presented in this blog is a trimmed down version of [Giphy for Slack](https://get.slack.help/hc/en-us/articles/204714258-Giphy-for-Slack). The (original) Giphy Slack app returns a bunch of GIFs for a search term and the user can pick one of them. To keep things simple, I've tweaked things a bit such that the serverless backend simply returns a (single) random image for a search keyword using the [Giphy Random API](https://developers.giphy.com/docs/#operation--gifs-random-get). 

Since the solution will be integrated as a [Slash Command](https://api.slack.com/interactivity/slash-commands) in Slack, the end user (you!) will invoke it from a Slack workspace using `/awsome <your search term>` (where `awsome` is nothing but the name of the slash command). This in turn invokes the Lambda function URL (the configuration is covered later in the blog), which takes care of the rest.

For example, invoking it from your Slack workspace using `/awsome serverless` will return a random GIF (you will try this later!)

Here is an overview of what the Lambda function does:

- Slack slash command invocation results in a `base64` encoded string payload being sent to the Lambda function URL - so the first step is to decode it.
- The function is only supposed to invoked by Slack and we need to make sure we confirm that. Slack makes this possible by allowing apps to verify requests using a signing secret - the function simply implements a Go version of the signature matching recipe presented [here](https://api.slack.com/authentication/verifying-requests-from-slack)
- If the signature match is successful (we return an error to the client if it fails), the Slack request is parsed to extract the search `text` that user sent.
- Then, the [Giphy Random API](https://developers.giphy.com/docs/api/endpoint#random) is invoked with the search term. If we get a successful response, we parse it and send it back to Slack in it's desired format
  
Finally, the user gets to see a GIF in their Slack workspace!

I will skip the code walk-through in order to focus on other aspects of the solution, but the function signature deserves a mention - it is similar to what you would've used in case of an API Gateway based solution:

```go
func Funcy(r events.LambdaFunctionURLRequest) (events.LambdaFunctionURLResponse, error) {
    ...
}
```

We are using [events.LambdaFunctionURLRequest](https://pkg.go.dev/github.com/aws/aws-lambda-go/events#LambdaFunctionURLRequest) as input and returning [events.LambdaFunctionURLResponse](https://pkg.go.dev/github.com/aws/aws-lambda-go/events#LambdaFunctionURLResponse). Behind the scenes, Lambda maps the request to an event object before passing it to the function. Finally, the function response is then mapped to an HTTP response that Lambda sends back to the client through the function URL.

> You can read up on the details in the [documentation](https://docs.aws.amazon.com/lambda/latest/dg/urls-invocation.html#urls-payloads)

That's quite convenient right? You can use API Gateway conventions without actually having to setup and configure one!

With that background info, let's move on to the part where you deploy the function and try it out with Slack. But, before that make sure you have the following ready:

## Pre-requisites

- [Create an AWS account](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) (if you do not already have one) and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
- Install and configure [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html)
- [Install Go](https://go.dev/dl/)
- [Install Git](https://git-scm.com/downloads)
- [Create a Slack workspace](https://slack.com/create) if you don't have one.
- Create a GIHPY account (it's free!) and [create an app](https://developers.giphy.com/dashboard/?create=true). Each application you create will have its own API Key.

> Please note down your GIPHY API key as you will be using it later

Clone the Github repo and move into the right directory:

```bash
git clone https://github.com/abhirockzz/awsome-slack-backend
cd awsome-slack-backend/function
```

> The subsequent steps use AWS CLI - I've purposely used the AWS CLI in order to highlight specific aspects of the process. Please check [this tutorial](https://docs.aws.amazon.com/lambda/latest/dg/urls-tutorial.html) for CloudFormation and SAM

## Build, zip and deploy the function!

```bash
export FUNC_NAME=awsome-slack-backend
export FUNC_GO_BINARY_NAME=awsome
export ZIP_NAME=function.zip

GOOS=linux go build -o $FUNC_GO_BINARY_NAME main.go

zip function.zip $FUNC_GO_BINARY_NAME
```

First, create an IAM Role for Lambda and attach the `AWSLambdaBasicExecutionRole` policy:

```bash
export ROLE_NAME=demo-lambda-role

ROLE_ARN=$(aws iam create-role \
        --role-name $ROLE_NAME \
        --assume-role-policy-document '{"Version": "2012-10-17","Statement": [{ "Effect": "Allow", "Principal": {"Service": "lambda.amazonaws.com"}, "Action": "sts:AssumeRole"}]}' \
        --query 'Role.[Arn]' --output text)

aws iam attach-role-policy --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
```

Create the function:

```bash
aws lambda create-function \
    --function-name $FUNC_NAME \
    --runtime go1.x \
    --zip-file fileb://$ZIP_NAME \
    --handler $FUNC_GO_BINARY_NAME \
    --role $ROLE_ARN
```

After the function gets created, go ahead and add the Function URL:

```bash
aws lambda create-function-url-config \
    --function-name $FUNC_NAME \
    --auth-type NONE
```

> For the purposes of this sample app, we're using `NONE` as the authentication type. This means that the Lambda function URL will be publicly accessible - more on this shortly


If you navigate to the AWS console and open the function you just created, you should see the Function URL associated with it:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7wu2ogjmxuxff8cr5uaq.png)

Let's invoke the function - copy the Function URL and paste it in a browser or use any other tool (e.g. `curl`)

```bash
curl -i <FUNCTION_URL>
```

You should get a `{"Message":"Forbidden"}` response with a `HTTP 403 Forbidden` status code

> Don't worry, this is expected - I wanted to make sure you encounter this issue and understand the root cause. 

Even though we use `NONE` as the authentication scheme, users must still have `lambda:InvokeFunctionUrl` permissions in order to successfully invoke the function URL. The (slightly) tricky bit is that when you create a function URL (with auth type `NONE`) via the console or AWS Serverless Application Model (AWS SAM), Lambda *automatically* creates the resource-based policy statement for you (details in the [documentation](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html#urls-auth-none)). That's not the case if you're using the AWS CLI (as in this blog), AWS CloudFormation, or the Lambda API directly - you must add permissions yourself.

Let's do that:

```bash
aws lambda add-permission \
    --function-name $FUNC_NAME \
    --action lambda:InvokeFunctionUrl \
    --statement-id FunctionURLAllowPublicAccess \
    --principal "*" \
    --function-url-auth-type NONE
```

> To see the policy, navigate to your Function in the AWS console: Configuration > Permissions

Invoke the function again:

```bash
curl -i <FUNCTION_URL>
```

This time, you will get a different error with a `HTTP 401 Unauthorized` status code. This is expected as well! 

Let's finish the rest of the configuration to get things working.

## Configure Slack

> Please note that most of the instructions in this section have been adapted from the [Slack documentation](https://api.slack.com/slash-commands#creating_commands)

Start by signing into your [Slack Workspace](https://slack.com/signin) and [creating a new Slack App](https://api.slack.com/apps/new).

Once that's done, create a Slash Command - 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 required information - Enter `/awsome` for the **Command** and enter the Lambda Function URL in **Request URL**


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t6l42xqqicso2rr4htvs.png)

Finally, install the app to your workspace - 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.

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

> Make a note of your app Signing Secret as you'll be using it later


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kqdlh0mbupiqb3ogfu78.png)

## Update the function

Now that you've the Slack signing secret key, you need to make sure to configure it in the function as well. Also, don't forget the GIPHY API key since the function needs that to invoke GIPHY REST endpoint.

Let's update the function to include these as environment variables:

```bash
aws lambda update-function-configuration \
    --function-name $FUNC_NAME \
    --environment "Variables={SLACK_SIGNING_SECRET=<enter Slack signing secret>,GIPHY_API_KEY=<enter Giphy API key>}"
```

> The sample app uses Lambda environment variables to store keys for Slack and GIPHY - this is just for demonstration purposes. You should use a solution such as [AWS Secrets Manager](https://aws.amazon.com/secrets-manager) to securely store and manage credentials. 

## You're all set!

Head over to your Slack workspace and invoke the command. For example, to get a random cat GIF, just type:

```bash
/awsome cat
```

I got this response. How about you? :-)


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rygtojf1uyts7flecr37.gif)

Feel free to play around with the app!

## Clean up

Once you're done, delete the function along with the IAM policy and role.

```bash
aws lambda delete-function --function-name $FUNC_NAME

aws iam detach-role-policy  --role-name $ROLE_NAME --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole

aws iam delete-role --role-name $ROLE_NAME
```

## That's all for now!

You configured and deployed a serverless backend for Slack and in the process, learnt about some of the aspects of Lambda Function URLs through the lens of this sample app. I would encourage you to explore other capabilities such as [AWS_IAM authentication](https://docs.aws.amazon.com/lambda/latest/dg/urls-auth.html#urls-auth-iam), [CORS config](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html#urls-cors), [throttling limits](https://docs.aws.amazon.com/lambda/latest/dg/urls-configuration.html#urls-throttling), [monitoring](https://docs.aws.amazon.com/lambda/latest/dg/urls-monitoring.html) etc.

Happy coding!