Skip to content
All articles

Building a Slack app: setup, permissions and a bounded first workflow

Hands using a laptop displaying a Slack conversation
Photo by Mikhail Nilov on Pexels

Build a Slack app around 4 decisions: its entry point, permissions, connection method and response. A small slash command is a useful first workflow because the user explicitly invokes it. This tutorial sketches a fictional collection-status lookup with Bolt for Python and Socket Mode. It does not connect to a real workspace or claim that an acknowledgement completes a business task.

The goal is deliberately narrow: /collection-status SAMPLE-A returns a fictional status to its invoker. It neither reads message history nor changes a collection. That boundary makes it easier to judge permissions and expected behaviour before adding customer systems. A bot that replies to every conversation is a different project with different access and privacy implications.

How do you configure a bounded first Slack app?

Use a development workspace where testing will not interrupt real work. Slack’s Bolt app guide describes creating an app, selecting the development workspace and configuring it through app settings. Installation and authorisation belong to someone authorised to manage that workspace. No account creation, installation or token generation was performed for this article.

Choose the connection method before filling in endpoints. Socket Mode delivers events and interactions over a WebSocket without a public HTTP request URL. Its documentation currently excludes Socket Mode apps from the public Slack Marketplace. This is an internal development example, not a Marketplace distribution recipe. A public HTTP integration has a separate request-verification boundary.

In your own authorised development configuration, enable Socket Mode. Create an app-level token with connections:write as documented, and keep it separate from the bot token issued through workspace installation. The first supports the connection; the second represents the bot’s granted workspace access. They are not interchangeable strings.

Add a slash command named /collection-status, a short description and a usage hint such as SAMPLE-A. The commands scope reference identifies commands for workspace shortcuts and slash commands. Configure that bot scope and complete the authorised installation flow. If the bot also needs ordinary channel posting, evaluate chat:write for that additional behaviour; the lookup below responds through the command response mechanism instead of adding a broad message listener.

Because this example uses Socket Mode, do not invent a public request URL to satisfy an HTTP tutorial. Confirm the enabled transport and command configuration together. Record changes to scopes and reinstall or reauthorise when the settings require it, rather than assuming the existing installation has silently gained new permissions.

Separate lookup logic from Slack transport

Create lookup.py with this original, network-free example. It uses invented demonstration references and returns only predefined text:

def collection_reply(text):
    reference = text.strip().upper()
    sample = {
        "SAMPLE-A": "Awaiting collection in the fictional demonstration.",
        "SAMPLE-B": "Cancelled in the fictional demonstration."
    }
    return sample.get(reference, "Use SAMPLE-A or SAMPLE-B for this demonstration.")

The absence of a database is intentional. This function cannot prove that an actual collection exists, authorise someone to view it or change its state. Keeping the logic separate lets you examine its decisions without a Slack account, credentials or outbound requests. A later production lookup must check the caller’s authority before exposing records.

In an isolated Python project, the development dependency command would be python -m pip install slack_bolt inside its virtual environment. This installation was not run here. Once an authorised operator has securely configured SLACK_BOT_TOKEN and SLACK_APP_TOKEN outside source control, a proposed app.py adapter is:

import os
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
from lookup import collection_reply

app = App(token=os.environ["SLACK_BOT_TOKEN"])

@app.command("/collection-status")
def collection_status(ack, respond, command):
    ack()
    respond({
        "response_type": "ephemeral",
        "text": collection_reply(command.get("text", ""))
    })

if __name__ == "__main__":
    SocketModeHandler(app, os.environ["SLACK_APP_TOKEN"]).start()

Slack’s command-listener documentation supports the decorator, acknowledgement and response helper pattern. The helper uses the command’s response URL. Treat that URL and tokens as sensitive capabilities; do not print complete payloads into an unrestricted log. Running this adapter would connect to Slack, so it is a separate authorised integration step, not an offline test.

How do you check responses without confusing them with completed work?

Slack’s acknowledgement guide requires prompt acknowledgement and documents a three-second timeout. Acknowledge before slow work. That deadline is a platform requirement, not an invented service target. Our immediate fictional lookup is small; a future external lookup needs a design for asynchronous results and failures.

The slash-command guide distinguishes an ephemeral response from an in_channel response. Explicitly selecting ephemeral makes the intended audience clear. It does not replace application-level access control, retention rules or a decision about whether the record should be disclosed at all. Custom slash commands also have invocation limits, including their unavailability inside message threads.

Check Expected decision Evidence to collect later
SAMPLE-A Fictional awaiting state Exact reply and intended recipient
SAMPLE-B Fictional cancelled state No collection action performed
Unknown or empty input Usage guidance No invented record
Slow external dependency Prompt acknowledgement, separate outcome Timing and failure handling
Unauthorised real-record request Refuse disclosure Caller-access decision

Original contribution: command acceptance card

Write a card for /collection-status with trigger, input, audience, data source, side effects and failure owner. Fill its first row with SAMPLE-A, ephemeral audience, fictional dictionary and no side effects. Fill a second row with an unknown reference: the expected reply is guidance, not a guessed status. Keep observed results blank until the relevant test actually runs.

Before replacing the dictionary with a business system, add cases for a different workspace, an unauthorised caller, a cancelled record and a dependency outage. A reply saying “request received” must not mark a collection ready. If a later command changes state, define duplicate handling and an audit trail before enabling that mutation. This lookup provides neither transactional guarantees nor delivery evidence.

For HTTP deployments, Slack’s request-verification guidance requires validating signed requests using the raw body and timestamp. Do not transplant an HTTP verification shortcut into Socket Mode or treat transport authentication as permission to expose every business record.

Use web development to frame the integration boundary and web design to describe the companion customer interface. To scope a small workflow, get started with the acceptance card and fictional payloads, leaving tokens and workspace conversations out of the enquiry.

By Spout Team. AI-assisted drafting by Codex using the linked primary sources.

Make your next move.

A website, a store or a clearer digital plan. Let’s start with your business.

Start your brief