Slack Events API
The @slack/events-api
package helps your app respond to events from Slack's Events API
such as new messages, emoji reactions, files, and much more. This package will help you start with convenient and secure
defaults.
Deprecation Notice
@slack/events-api
officially reached EOL on May 31st, 2021. Development has fully stopped for this package and all remaining open issues and pull requests have been closed.
At this time, we recommend migrating to Bolt for JavaScript, a framework that offers all of the functionality available in those packages (and more). To help with that process, we've provided some migration samples for those looking to convert their existing apps.
Installation
$ npm install @slack/events-api
Usage
These examples show how to get started using the most common features. You'll find even more extensive
documentation on the package's website.
Before building an app, you'll need to create a Slack app and install it to your
development workspace. You'll also need a public URL where the app can begin receiving events. Finally, you'll need
to find the request signing secret given to you by Slack under the "Basic Information" of your app configuration.
It may be helpful to read the tutorials on getting started and
getting a public URL that can be used for development.
After you have a URL for development, see the section on verifying a request URL for development so you
can save it as the Request URL in your app configuration. Now you can begin adding event subscriptions, just be sure to
install the app in your development workspace again each time you add new scopes (typically whenever you add new event
subscriptions).
Initialize the event adapter
The package exports a createEventAdapter()
function, which returns an instance of the SlackEventAdapter
class. The function
requires one parameter, the request signing secret, which it uses to enforce that all events are coming from Slack
to keep your app secure.
const { createEventAdapter } = require('@slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackEvents = createEventAdapter(slackSigningSecret);
Start a server
The event adapter transforms incoming HTTP requests into verified and parsed events.
That means, in order for it to emit events for your app, it needs an HTTP server. The adapter can receive requests from
an existing server, or as a convenience, it can create and start the server for you.
In the following example, the event adapter starts an HTTP server using the .start()
method. Starting the
server requires a port
for it to listen on. This method returns a Promise
which resolves for an instance of an
HTTP server once it's ready to emit events. By default,
the built-in server will be listening for events on the path /slack/events
, so make sure your Request URL ends with
this path.
const { createEventAdapter } = require('@slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackEvents = createEventAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
(async () => {
const server = await slackEvents.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
Note: To gracefully stop the server, there's also the .stop()
method, which returns a Promise
that resolves
when the server is no longer listening.
Using an existing HTTP server
The event adapter can receive requests from an existing Node HTTP server. You still need to specify a port, but this
time its only given to the server. Starting a server in this manner means it is listening to requests on all paths; as
long as the Request URL is routed to this port, the adapter will receive the requests.
const { createServer } = require('http');
const { createEventAdapter } = require('@slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackEvents = createEventAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
const server = createServer(slackEvents.requestListener());
server.listen(port, () => {
console.log(`Listening for events on ${server.address().port}`);
});
Using an Express app
The event adapter can receive requests from an Express application. Instead of plugging the
adapter's request listener into a server, it's plugged into the Express app
. With Express, app.use()
can be used to
set which path you'd like the adapter to receive requests from. You should be careful about one detail: if your
Express app is using the body-parser
middleware, then the adapter can only work if it comes before the body parser
in the middleware stack. If you accidentally allow the body to be parsed before the adapter receives it, the adapter
will emit an error, and respond to requests with a status code of 500
.
const { createServer } = require('http');
const express = require('express');
const bodyParser = require('body-parser');
const { createEventAdapter } = require('@slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const port = process.env.PORT || 3000;
const slackEvents = createEventAdapter(slackSigningSecret);
const app = express();
app.use('/my/path', slackEvents.requestListener());
app.use(bodyParser());
const server = createServer(app);
server.listen(port, () => {
console.log(`Listening for events on ${server.address().port}`);
});
Listen for an event
Apps register functions, called listeners, to be triggered when an event of a specific type is received by the
adapter. If you've used Node's EventEmitter
pattern
before, then you're already familiar with how this works, since the adapter is an EventEmitter
.
The event
argument passed to the listener is an object. It's contents corresponds to the type of
event its registered for.
const { createEventAdapter } = require('@slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackEvents = createEventAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
slackEvents.on('message', (event) => {
console.log(`Received a message event: user ${event.user} in channel ${event.channel} says ${event.text}`);
});
(async () => {
const server = await slackEvents.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
Handle errors
If an error is thrown inside a listener, it must be handled, otherwise it will crash your program. The adapter allows
you to define an error handler for errors thrown inside any listener, using the .on('error', handlernFn)
method.
It's a good idea to, at the least, log these errors so you're aware of what happened.
const { createEventAdapter } = require('@slack/events-api');
const slackSigningSecret = process.env.SLACK_SIGNING_SECRET;
const slackEvents = createEventAdapter(slackSigningSecret);
const port = process.env.PORT || 3000;
slackEvents.on('message', (event) => {
event.notAMethod();
});
slackEvents.on('error', (error) => {
console.log(error.name);
});
(async () => {
const server = await slackEvents.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
Debugging
If you're having difficulty understanding why a certain request received a certain response, you can try debugging your
program. A common cause is a request signature verification failing, sometimes because the wrong secret was used. The
following example shows how you might figure this out using debugging.
Start your program with the DEBUG
environment variable set to @slack/events-api:*
. This should only be used for
development/debugging purposes, and should not be turned on in production. This tells the adapter to write messages
about what its doing to the console. The easiest way to set this environment variable is to prepend it to the node
command when you start the program.
$ DEBUG=@slack/events-api:* node app.js
app.js
:
const { createEventAdapter } = require('@slack/events-api');
const port = process.env.PORT || 3000;
const slackEvents = createEventAdapter('not a real signing secret');
slackEvents.on('message', (event) => {
console.log(`Received a message event: user ${event.user} in channel ${event.channel} says ${event.text}`);
});
(async () => {
const server = await slackEvents.start(port);
console.log(`Listening for events on ${server.address().port}`);
})();
When the adapter receives a request, it will now output something like the following to the console:
@slack/events-api:adapter adapter instantiated - options: { includeBody: false, includeHeaders: false, waitForResponse: false }
@slack/events-api:adapter server created - path: /slack/events
@slack/events-api:adapter server started - port: 3000
@slack/events-api:http-handler request received - method: POST, path: /slack/events
@slack/events-api:http-handler request signature is not valid
@slack/events-api:http-handler handling error - message: Slack request signing verification failed, code: SLACKHTTPHANDLER_REQUEST_SIGNATURE_VERIFICATION_FAILURE
@slack/events-api:http-handler sending response - error: Slack request signing verification failed, responseOptions: {}
This output tells the whole story of why the adapter responded to the request the way it did. Towards the end you can
see that the signature verification failed.
If you believe the adapter is behaving incorrectly, before filing a bug please gather the output from debugging and
include it in your bug report.
Verify tool
Once you have a URL where you'd like to receive requests from the Events API, you must save it as a Request URL in your
Slack app configuration. But in order to save it, your app needs to respond to a challenge request, so that Slack knows
its your app that owns this URL. How can you do that if you haven't built the app yet? For development, there is a
command line tool built into this package that you can use to respond to the challenge.
Once the package is installed in your app, a command line program will be available in your node_modules
directory.
$ ./node_modules/.bin/slack-verify --secret <signing_secret> [--path=/slack/events] [--port=3000]
Run the command with your own signing secret (provided by Slack in the "Basic Information"), and optionally a path and a
port. A web server will be listening for requests containing a challenge and respond to them the way Slack expects. Now
input input and save the Request URL. Once its saved, you can stop the server with Ctrl-C
and start working on your
app.
Note: If you're using a tunneling tool like ngrok, the Request URL you save in Slack would be
the tunnel URL, such as https://abcdef.ngrok.io
, appended with the path. In other words, it should look like
https://abcdef.ngrok.io/slack/events
. Also make sure that when ngrok was started, it's set to use the port that the
tool is listening on. In other words, start ngrok with a command like ngrok http 3000
.
More
The documentation website has information about these additional features of
the SlackEventAdapter
:
- Receiving event envelope and header data
- Custom responses
Examples
- Greet And React - A ready to run sample app that listens for messages and emoji
reactions, and responds to them. It is built on top of the Express web framework. It also
implements OAuth to demonstrate how an app can handle installation to additional
workspaces and be structured to handle events from multiple workspaces.
Requirements
This package supports Node v8 LTS and higher. It's highly recommended to use the latest LTS version of
node, and the documentation is written using syntax and features
from that version.
Getting Help
If you get stuck, we're here to help. The following are the best ways to get assistance working through your issue:
- Issue Tracker for questions, feature requests, bug reports and
general discussion related to these packages. Try searching before you create a new issue.
- Email us in Slack developer support:
developers@slack.com
- Bot Developers Hangout: a Slack community for developers
building all types of bots. You can find the maintainers and users of these packages in #sdk-node-slack-sdk.