This article describes an outdated approach to Slack apps. New Slack apps act independently of a user token. Build a bot user powered by only the specific permissions it needs. Check out new Slack apps now.
Enable conversations between users and apps in Slack by building bots.
A bot is a type of Slack App designed to interact with users via conversation.
A bot is the same as a regular app: it can access the same range of APIs and do all of the magical things that a Slack App can do.
But when you build a bot for your Slack App, you're giving that app a face, a name, and a personality, and encouraging users to talk to it.
Your bot can send DMs, it can be mentioned by users, it can post messages or upload files, and it can be invited to channels - or kicked out.
Bots are not cybernetic infiltration units, and it is unlikely that they dream of electric sheep, though we can't rule it out. 🤖
Since your bot is capable of doing everything that a Slack App can do, we're going to limit our focus to a common use-case for bots. The following steps will get you to the point where you have a bot waiting for messages with trigger words and sending simple responses.
From there, you can start bolting on whatever kind of amazing app logic or nuclear-powered jet-packs you can imagine.
Before you start, you'll need a Slack App. If you don't already have one, click the following button to create it:
That done, you're all ready to arrange the architecture of this automaton.
To use your Slack App as a bot, first you'll need to create a Bot User for it.
Head to your app's settings page and click the Bot Users feature in the navigation menu.
You'll be presented with a button marked Add a Bot User, and when you click on it, you'll see a screen where you can configure your app's bot user with the following info:
Once you've completed these fields, click the Add Bot User button and then Save Changes.
Great, you've just created a bouncing baby bot! Don't leave the app settings yet though, there's just one more bit of configuration left to do.
The Events API is a bot's equivalent of eyes and ears. It gives a bot a way to react to posted messages, changes to channels, and other activities that happen in Slack. When these events happen, a data payload will be sent to your bot, and it can use that data to form a useful response.
Giving your bot access to the Events API is pretty simple:
app_mention
which sends events when someone mentions your bot, and message.channels
which sends events when a new message is posted in a public channel.Good news! Your bot is looking more and more life-like, and now it's ready to find a home.
A bot user is added to a workspace by installing the app that the bot is associated with. Once you do, you'll get a bot token that is imbued with the bot
scope. This token can be used with a subset of Web API methods that we'll discuss later.
If you had already installed your app in the past, you'll need to reinstall to grant the additional bot
scope. The process is the same either way:
Once installed, you will have generated a bot token that you should store for use later on - you can find it in your app's settings under Install App > Bot User OAuth Access Token.
Bot tokens can also be generated using the OAuth install flow if you are distributing your app beyond your own workspace.
Your bot should now be happily dwelling in the channel picked during the install process. It will be listening out for users posting in that channel, and for posted messages that mention the bot. Now you need to tell it what to do when it hears something.
In a previous step, we configured the event subscriptions for your app, but now we have to actually do something with the data that will be sent with each event.
Let's imagine a simple conversational bot that responds to being mentioned by sending a couple of follow up messages:
There are 4 events triggered in this conversation: the first is an app_mention
event from the first message that mentions the bot; the next three are message
events for each of the messages posted by Johnny.
Our bot will need to be able to interpret each event and respond accordingly.
We've avoided showing you any specific code up until now, but in the following steps we're going to explain the process and then show very simplified Express/Node.js examples of what your app logic should look like. These examples translate readily into most modern programming languages.
The first thing we need to do is create some app code that will correctly receive the events.
Each event will trigger a request, containing a JSON payload, sent to your configured Request URL. The Events API docs contain a full description of the shape of this JSON, and the reference for app_mention
and the message.channels
contain any details specific to each event type.
Your app has to be able to receive and parse this JSON, and then send an immediate confirmation response to each event request, as described in the Events API docs.
Here's how we might build our code for receiving events:
// Receive event payload to Request URL via HTTP POST
router.post("/", function(req, res, next) {
// Get event payload
let payload = req.body;
// Respond to this event with HTTP 200 status
res.sendStatus(200);
}
Now that you've written code to handle an event, you can think about how to respond in a 'bot-like' way.
For a bot, being mentioned is usually the triggering event for a conversation, just as a human will respond when they hear their name.
Your app code should use the type
field inside the event payload to spot these app_mention
events, and differentiate them from any other events it might receive.
In addition, you don't want to respond to every mention, only the ones that are actually intended to trigger the "tell a joke" flow.
To do that, use the text
field from the event payload, which contains the text of the message that the mention was contained in. When text
mentions the bot and includes the words "tell me a joke", the bot will respond; otherwise it'll just stay quiet.
Here's what the example code might look like with this kind of logic:
router.post("/", function(req, res, next) {
let payload = req.body;
res.sendStatus(200);
if (payload.event.type === "app_mention") {
if (payload.event.text.includes("tell me a joke")) {
// Make call to chat.postMessage using bot's token
}
}
}
With the call to chat.postMessage
, the first line of the joke is sent:
Knock, knock.
To send this, your app should use that API method with the token you stored earlier. Here's an example request:
POST https://slack.com/api/chat.postMessage
Content-type: application/json
Authorization: Bearer YOUR_BOTS_TOKEN
{
"text": "Hello <@UA8RXUPSP>! Knock, knock.",
"channel": "C123ABC456"
}
You can read the API method reference for more info on building this request. You can, of course, use all the special formatting, attachments, and interactive components available for messages, but for now keep it simple.
So, your bot has uttered those first magical words, and you can assume that the user will reply with the standard "Who's there?" response. Let's find out how to keep the joke going.
As we said before, the flow we're describing contains an app_mention
event followed by three message
events. In order to identify the differences between those three messages, the app logic must become a bit more complex.
The first thing you need to do is use the type
field inside the event payload to look for these message
events.
Next, use the text
of the message in the event payload to decide which kind of response your bot should make.
Again, let's assume the pattern of 'knock, knock' jokes - the first user response is always "Who's there?", and the second user response is always "____ who?". So, you can check for messages that include these words, and use the right bot response for each. If you see any messages that don't include either of these phrases, ignore them.
Added to the code from previous steps you'll have something like this:
router.post("/", function(req, res, next) {
let payload = req.body;
res.sendStatus(200);
if (payload.event.type === "app_mention") {
if (payload.event.text.includes("tell me a joke")) {
// Make call to chat.postMessage using bot's token
}
}
if (payload.event.type === "message") {
let response_text;
if (payload.event.text.includes("Who's there?")) {
response_text = "A bot user";
}
if (payload.event.text.includes("Bot user who?")) {
response_text = "No, I'm a bot user. I don't understand jokes.";
}
if (response_text !== undefined) {
// Make call to chat.postMessage sending response_text using bot's token
}
}
}
Congratulations, your first bot is now alive and talking! You should now be able to go to the channel you installed the bot into and strike up this conversation with it. Remember to laugh politely when it tells you the punchline.
Your next steps should involve adding some more complexity to your bot to make it useful.
In the steps above, we made a lot of assumptions of simplicity. For example, we expected that users would respond with a very specific spelling, we assumed a test environment where there were no other conversations happening, and so on.
For a real bot in production, some of these assumptions would break the behavior of the bot. So let's cover some situations that you should address for your own bots - think of these as best practices rather than specific instructions to follow.
In our example bot, we've used a mention as the triggering point for a specific conversation, but you'll notice that your bot will still respond if you skip some of the steps - for example if you type Who's there?
, your bot will respond to this message with A bot user
, even if you didn't mention the bot or start at the beginning of the conversation.
A solution to this might involve tracking the beginning of a conversation, the participants involved, and the progress through the flow. For example, when the user first mentions the bot, a database entry is created that identifies that user and the open workflow with them.
As the user progresses through the flow, the database records this, and the user is unable to repeat earlier steps in the conversation (unless of course that is a desired behavior). Once the workflow is completed, the database entry is also marked as complete, and the bot waits for another mention before starting anew.
Be aware that a user might choose to reply to your bot's messages in a thread rather than at the channel-level. Your bot will still receive message
events for these threaded replies, but you will have to add some extra logic to ensure that your bot responds to the user in the relevant location.
Check out the Threading Messages docs for more information on how to spot the difference between messages and threaded messages.
Because your bot will be interacting with humans, it's unlikely that you can expect consistent spelling and phrasing across messages from different people that might be trying to invoke the same thing. For example, our simple example bot used the phrase tell me a joke
to trigger the start of the workflow, but at a very basic level a user might also try typing what's a good joke?
or make me laugh
.
Your bot can get more complex by broadening its understanding of natural language queries to capture a wider range of potential trigger phrases. Alternatively you can be more prescriptive about the exact phrasing to use, and provide user education to train correct usage.
The real magic of a bot comes when it is connected with external services, providing a seamless conversational interface for them from within Slack.
There's a huge range of possibilities for what your bot could do, so it might help to start with a great resource like the Botkit Community.
Like other APIs and integrations, bot users are free. Unlike regular users, the actions they can perform are somewhat limited. For workspaces on the Free plan, each bot user counts as a separate integration.
Bot Users, and bot tokens, can be used with a slightly restricted set of Web API methods. These methods are shown below:
Method & Description | Description |
---|---|
apps.auth.external.delete Delete external auth tokens only on the Slack side | Delete external auth tokens only on the Slack side |
apps.auth.external.get Get the access token for the provided token ID | Get the access token for the provided token ID |
apps.datastore.delete Delete an item from a datastore | Delete an item from a datastore |
apps.datastore.get Get an item from a datastore | Get an item from a datastore |
apps.datastore.put Creates a new item, or replaces an old item with a new item. | Creates a new item, or replaces an old item with a new item. |
apps.datastore.query Query a datastore for items | Query a datastore for items |
apps.datastore.update Edits an existing item's attributes, or adds a new item if it does not already exist. | Edits an existing item's attributes, or adds a new item if it does not already exist. |
apps.uninstall Uninstalls your app from a workspace. | Uninstalls your app from a workspace. |
auth.revoke Revokes a token. | Revokes a token. |
auth.teams.list Obtain a full list of workspaces your org-wide app has been approved for. | Obtain a full list of workspaces your org-wide app has been approved for. |
auth.test Checks authentication & identity. | Checks authentication & identity. |
bookmarks.add Add bookmark to a channel. | Add bookmark to a channel. |
bookmarks.edit Edit bookmark. | Edit bookmark. |
bookmarks.list List bookmark for the channel. | List bookmark for the channel. |
bookmarks.remove Remove bookmark from the channel. | Remove bookmark from the channel. |
bots.info Gets information about a bot user. | Gets information about a bot user. |
calls.add Registers a new Call. | Registers a new Call. |
calls.end Ends a Call. | Ends a Call. |
calls.info Returns information about a Call. | Returns information about a Call. |
calls.participants.add Registers new participants added to a Call. | Registers new participants added to a Call. |
calls.participants.remove Registers participants removed from a Call. | Registers participants removed from a Call. |
calls.update Updates information about a Call. | Updates information about a Call. |
channels.create Creates a channel. | Creates a channel. |
channels.info Gets information about a channel. | Gets information about a channel. |
channels.invite Invites a user to a channel. | Invites a user to a channel. |
channels.mark Sets the read cursor in a channel. | Sets the read cursor in a channel. |
chat.delete Deletes a message. | Deletes a message. |
chat.deleteScheduledMessage Deletes a pending scheduled message from the queue. | Deletes a pending scheduled message from the queue. |
chat.getPermalink Retrieve a permalink URL for a specific extant message | Retrieve a permalink URL for a specific extant message |
chat.meMessage Share a me message into a channel. | Share a me message into a channel. |
chat.postEphemeral Sends an ephemeral message to a user in a channel. | Sends an ephemeral message to a user in a channel. |
chat.postMessage Sends a message to a channel. | Sends a message to a channel. |
chat.scheduleMessage Schedules a message to be sent to a channel. | Schedules a message to be sent to a channel. |
chat.scheduledMessages.list Returns a list of scheduled messages. | Returns a list of scheduled messages. |
chat.unfurl Provide custom unfurl behavior for user-posted URLs | Provide custom unfurl behavior for user-posted URLs |
chat.update Updates a message. | Updates a message. |
conversations.acceptSharedInvite Accepts an invitation to a Slack Connect channel. | Accepts an invitation to a Slack Connect channel. |
conversations.approveSharedInvite Approves an invitation to a Slack Connect channel | Approves an invitation to a Slack Connect channel |
conversations.archive Archives a conversation. | Archives a conversation. |
conversations.close Closes a direct message or multi-person direct message. | Closes a direct message or multi-person direct message. |
conversations.create Initiates a public or private channel-based conversation | Initiates a public or private channel-based conversation |
conversations.declineSharedInvite Declines a Slack Connect channel invite. | Declines a Slack Connect channel invite. |
conversations.history Fetches a conversation's history of messages and events. | Fetches a conversation's history of messages and events. |
conversations.info Retrieve information about a conversation. | Retrieve information about a conversation. |
conversations.invite Invites users to a channel. | Invites users to a channel. |
conversations.inviteShared Sends an invitation to a Slack Connect channel | Sends an invitation to a Slack Connect channel |
conversations.join Joins an existing conversation. | Joins an existing conversation. |
conversations.kick Removes a user from a conversation. | Removes a user from a conversation. |
conversations.leave Leaves a conversation. | Leaves a conversation. |
conversations.list Lists all channels in a Slack team. | Lists all channels in a Slack team. |
conversations.listConnectInvites Lists shared channel invites that have been generated or received but have not been approved by all parties | Lists shared channel invites that have been generated or received but have not been approved by all parties |
conversations.mark Sets the read cursor in a channel. | Sets the read cursor in a channel. |
conversations.members Retrieve members of a conversation. | Retrieve members of a conversation. |
conversations.open Opens or resumes a direct message or multi-person direct message. | Opens or resumes a direct message or multi-person direct message. |
conversations.rename Renames a conversation. | Renames a conversation. |
conversations.replies Retrieve a thread of messages posted to a conversation | Retrieve a thread of messages posted to a conversation |
conversations.setPurpose Sets the purpose for a conversation. | Sets the purpose for a conversation. |
conversations.setTopic Sets the topic for a conversation. | Sets the topic for a conversation. |
conversations.unarchive Reverses conversation archival. | Reverses conversation archival. |
dialog.open Open a dialog with a user | Open a dialog with a user |
dnd.info Retrieves a user's current Do Not Disturb status. | Retrieves a user's current Do Not Disturb status. |
dnd.teamInfo Retrieves the Do Not Disturb status for up to 50 users on a team. | Retrieves the Do Not Disturb status for up to 50 users on a team. |
emoji.list Lists custom emoji for a team. | Lists custom emoji for a team. |
files.comments.delete Deletes an existing comment on a file. | Deletes an existing comment on a file. |
files.completeUploadExternal Finishes an upload started with files.getUploadURLExternal | Finishes an upload started with files.getUploadURLExternal |
files.delete Deletes a file. | Deletes a file. |
files.getUploadURLExternal Gets a URL for an edge external file upload | Gets a URL for an edge external file upload |
files.info Gets information about a file. | Gets information about a file. |
files.list List for a team, in a channel, or from a user with applied filters. | List for a team, in a channel, or from a user with applied filters. |
files.remote.add Adds a file from a remote service | Adds a file from a remote service |
files.remote.info Retrieve information about a remote file added to Slack | Retrieve information about a remote file added to Slack |
files.remote.list Retrieve information about a remote file added to Slack | Retrieve information about a remote file added to Slack |
files.remote.remove Remove a remote file. | Remove a remote file. |
files.remote.share Share a remote file into a channel. | Share a remote file into a channel. |
files.remote.update Updates an existing remote file. | Updates an existing remote file. |
files.upload Uploads or creates a file. | Uploads or creates a file. |
groups.create Creates a private channel. | Creates a private channel. |
groups.info Gets information about a private channel. | Gets information about a private channel. |
groups.invite Invites a user to a private channel. | Invites a user to a private channel. |
groups.mark Sets the read cursor in a private channel. | Sets the read cursor in a private channel. |
groups.open Opens a private channel. | Opens a private channel. |
im.list Lists direct message channels for the calling user. | Lists direct message channels for the calling user. |
im.mark Sets the read cursor in a direct message channel. | Sets the read cursor in a direct message channel. |
im.open Opens a direct message channel. | Opens a direct message channel. |
migration.exchange For Enterprise Grid workspaces, map local user IDs to global user IDs | For Enterprise Grid workspaces, map local user IDs to global user IDs |
mpim.list Lists multiparty direct message channels for the calling user. | Lists multiparty direct message channels for the calling user. |
mpim.mark Sets the read cursor in a multiparty direct message channel. | Sets the read cursor in a multiparty direct message channel. |
mpim.open This method opens a multiparty direct message. | This method opens a multiparty direct message. |
pins.add Pins an item to a channel. | Pins an item to a channel. |
pins.list Lists items pinned to a channel. | Lists items pinned to a channel. |
pins.remove Un-pins an item from a channel. | Un-pins an item from a channel. |
reactions.add Adds a reaction to an item. | Adds a reaction to an item. |
reactions.get Gets reactions for an item. | Gets reactions for an item. |
reactions.list Lists reactions made by a user. | Lists reactions made by a user. |
reactions.remove Removes a reaction from an item. | Removes a reaction from an item. |
stars.add Save an item for later. Formerly known as adding a star. | Save an item for later. Formerly known as adding a star. |
team.billing.info Reads a workspace's billing plan information. | Reads a workspace's billing plan information. |
team.info Gets information about the current team. | Gets information about the current team. |
team.preferences.list Retrieve a list of a workspace's team preferences. | Retrieve a list of a workspace's team preferences. |
team.profile.get Retrieve a team's profile. | Retrieve a team's profile. |
usergroups.create Create a User Group | Create a User Group |
usergroups.disable Disable an existing User Group | Disable an existing User Group |
usergroups.enable Enable a User Group | Enable a User Group |
usergroups.list List all User Groups for a team | List all User Groups for a team |
usergroups.update Update an existing User Group | Update an existing User Group |
usergroups.users.list List all users in a User Group | List all users in a User Group |
usergroups.users.update Update the list of users for a User Group | Update the list of users for a User Group |
users.conversations List conversations the calling user may access. | List conversations the calling user may access. |
users.getPresence Gets user presence information. | Gets user presence information. |
users.info Gets information about a user. | Gets information about a user. |
users.list Lists all users in a Slack team. | Lists all users in a Slack team. |
users.lookupByEmail Find a user with an email address. | Find a user with an email address. |
users.profile.get Retrieve a user's profile information, including their custom status. | Retrieve a user's profile information, including their custom status. |
users.setActive Marked a user as active. Deprecated and non-functional. | Marked a user as active. Deprecated and non-functional. |
users.setPresence Manually sets user presence. | Manually sets user presence. |
views.open Open a view for a user. | Open a view for a user. |
views.publish Publish a static view for a User. | Publish a static view for a User. |
views.push Push a view onto the stack of a root view. | Push a view onto the stack of a root view. |
views.update Update an existing view. | Update an existing view. |
workflows.stepCompleted Indicate that an app's step in a workflow completed execution. | Indicate that an app's step in a workflow completed execution. |
workflows.stepFailed Indicate that an app's step in a workflow failed to execute. | Indicate that an app's step in a workflow failed to execute. |
workflows.updateStep Update the configuration for a workflow step. | Update the configuration for a workflow step. |
Method & Description | Description |
---|---|
api.test Checks API calling code. | Checks API calling code. |
auth.test Checks authentication & identity. | Checks authentication & identity. |
bots.info Gets information about a bot user. | Gets information about a bot user. |
calls.add Registers a new Call. | Registers a new Call. |
calls.end Ends a Call. | Ends a Call. |
calls.info Returns information about a Call. | Returns information about a Call. |
calls.participants.add Registers new participants added to a Call. | Registers new participants added to a Call. |
calls.participants.remove Registers participants removed from a Call. | Registers participants removed from a Call. |
calls.update Updates information about a Call. | Updates information about a Call. |