README
Asynchronous Slackbot
A simple, asynchronous back end for your Slack app.
The app intentionally does very little: it is essentially middleware for ExpressJS that accepts an incoming request, verifies its origin, and passes the request to a user-provided callback, where the payload is sent to a queue/trigger for asynchronous processing.
Endpoints are provided for:
GET /health
check to verify the service is runningGET /install
begin the process of installing your app to a workspaceGET /oauth
completes the OAuth2 workflowGET /oauth/v2
completes the OAuth2 workflow (v2)POST /callbacks
publishes interactive messagesPOST /events
publishes events from the Events APIPOST /slash/:cmd
publishes slash commands
In production it is expected that users will attach their own publishing functions to connect to a messaging service like Amazon EventBridge, or Google Pub/Sub.
Advantages
- Separates the concerns of responding to incoming requests and the logic to handle them.
- Handlers can be added/removed independently of this app; deploy once and forget.
- Requests can be published to any platform.
- Handlers can be written in any language supported by the topic trigger.
- Designed to work within serverless frameworks, such as AWS Lambda or Google Cloud Functions.
- Authenticates requests using Slack's signing secrets so you'll know that events published to internal triggers/queues are verified.
Drawbacks
- Slack has a strict 3-second lifetime for many API operations, so it is critical that your asynchronous tasks complete quickly. Cold start times of some serverless computing platforms may be prohibitively slow. (Note: this concern can be effectively eliminated on most platforms by configuring your serverless functions for speed)
Processing Events
In very simple terms, all events are processed by transforming the request payload to a JSON object and assigning it to the express local res.locals.slack
.
It is left to the user to handle further transformation and publishing of the event, however the aws
module provides an implementation that publishes the events to EventBridge.
Here is an example configuration that simply responds to incoming requests with the processed payload:
const express = require("express");
const slackend = require("slackend");
const app = express();
app.use(slackend(), (req, res) => res.json(res.locals.slack));
app.listen(3000);
Serverless Deployment
Deploying a version of this app to Amazon Web Services (AWS) serverless offerings might take the above shape, where incoming requests from Slack to your app are handled as follows:
API Gateway receives and routes all requests to a single Lambda function integration.
On cold starts, the Lambda function pulls its Slack tokens/secrets from its encrypted SecretsManager secret, starts a proxy express server, and publishes the request to an EventBridge bus.
On warm starts the environment and server are cached and the request is published to EventBridge without needing to re-fetch the app secrets.
Once the request is published, the API sends a 204 NO CONTENT
response back to Slack.
Using this method, each feature of your app can be added one-by-one independently of the API and is highly scalable.
NodeJS Usage
At its core, slackend
is middleware for ExpressJS with several routes predefined for handling Slack messages. None of the routes are configured to respond to the request. This is done deliberately so users can customize the behavior of the app.
The Slack message and an inferred topic name are stored in the res.locals
object and can be used to publish the request to your preferred messaging/queueing service.
Here is an example usage that simply logs the request to the console:
const slackend = require("slackend");
// Create express app
const app = slackend({
client_id: process.env.SLACK_CLIENT_ID,
client_secret: process.env.SLACK_CLIENT_SECRET,
oauth_error_uri: process.env.SLACK_OAUTH_ERROR_URI,
oauth_redirect_uri: process.env.SLACK_OAUTH_REDIRECT_URI,
oauth_success_uri: process.env.SLACK_OAUTH_SUCCESS_URI,
signing_secret: process.env.SLACK_SIGNING_SECRET,
signing_version: process.env.SLACK_SIGNING_VERSION,
token: process.env.SLACK_TOKEN,
});
// You *must* add a callback that responds to the request
app.use((req, res) => {
console.log(res.locals);
res.json({ ok: true });
});
WARNING — All of the configuration options to slackend()
are optional, but omitting the signing_secret
will disable the verification step where received requests are confirmed as originating from Slack. Disabling verification can also be done by setting the environmental variable DISABLE_VERIFICATION=1
.
Local Development
Run a local instance of your slack app by cloning this repository, configuring settings, installing dependencies, and starting the express server.
Configure settings by copying .env.example
to .env
and adding your keys/settings.
cp .env.example .env
Install dependencies using npm
or docker-compose
:
npm install
Start the server:
npm start
Send a sample request:
# Callback
curl --request POST \
--data 'payload=%7B%22callback_id%22%3A%22fizz%22%7D' \
--url 'http://localhost:3000/callbacks'
# Event
curl --request POST \
--header 'Content-Type: application/json' \
--data '{"type": "event_callback", "event": {"type": "team_join"}}' \
--url 'http://localhost:3000/events'
# Slash command
curl --request POST \
--data 'fizz=buzz' \
--url 'http://localhost:3000/slash/fizz'
AWS
A module is provided to deploy to Lambda using SecretsManager to store the Slack secrets.
Example Lambda handler:
const slackend = require("slackend/aws");
module.exports = slackend();
Deploy with Terraform
Deploy directly to AWS using terraform
and the slackbot
+ slackbot-secrets
modules:
resource "aws_apigatewayv2_api" "slackbot_api" {
name = "my-slack-api"
protocol_type = "HTTP"
# …
}
module "slackbot" {
source = "amancevice/slackbot/aws"
version = "~> 20.1"
http_api_execution_arn = aws_apigatewayv2_api.http_api.execution_arn
http_api_id = aws_apigatewayv2_api.http_api.id
lambda_function_name = "my-function-name"
role_name = "my-role-name"
secret_name = module.slackbot_secrets.secret.name
topic_name = "my-topic-name"
# …
}
module "slackbot_secrets" {
source = "amancevice/slackbot-secrets/aws"
kms_key_alias = "alias/slack/your-kms-key-alias"
secret_name = "slack/your-secret-name"
slack_bot_token = "your-bot-token"
slack_client_id = "your-client-id"
slack_client_secret = "your-client-secret"
slack_signing_secret = "your-signing-secret"
slack_user_token = "your-user-token"
// Optional additional secrets
secrets = { FIZZ = "buzz" }
}