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 messages
POST /events
publishes events from the Events API
POST /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.
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);
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.
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
.
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'
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 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" }
}