Publish interactive notifications

Intermediate

In this tutorial, you'll see how you can build an app that can publish notification messages that contain interactive elements. We'll show you how apps can respond to the use of those interactive elements and use them to trigger useful flows.

Create a pre-configured app

Quickly create an app with the correct configuration of scopes and features for this tutorial by clicking below.

Step 1Use Bolt to build your app

Bolt is the swiftest way to start building on the Slack Platform. Before you proceed with this tutorial, follow our Getting Started with Bolt for JavaScript, Python, and Java guides to get setup and ready to build.

    Step complete!

    Step 2Compose your interactive notification

    Your Slack app can publish text messages to public channels or private conversations just as users can. These messages can be used as notifications for your app.

    Apps can also include special visual components in their messages, using a framework we call Block Kit. Block Kit contains a subset of interactive components that allow users to take actions within your app, directly from a conversation.

    Composing a notification message involves choosing blocks, and laying them out to present important data. In doing so, you’ll create an array of Block Kit objects that will be used when publishing a message.

    When you're done

    After you've finished with this section, you will have created an interactive message payload to publish in the next section.

    • Create a basic message payload

      All of the Slack APIs that publish messages use a common structure, called a message payload.

      This payload is a JSON object that is used to define the content of the message and metadata about the message. The metadata can include required information such as the conversation the message should be published to, and optional parameters which determine the visual composition of the message.

      Here's a very basic app-published message payload:

      {
        "channel": "CONVERSATION_ID",
        "text": "Hello, world.",
      }
      

      You can go further to customize published messages by using special text formatting, or by composing a blocks parameter to define a rich, and potentially interactive, message layout.

      View our message payload reference for a list of potential payload parameters.

    • OptionalFormat text in your message

      Text within message payloads can be formatted using a special markup syntax called mrkdwn. Consult this reference guide to learn everything you can do with mrkdwn.

    • Add a rich layout to your message using Block Kit

      Slack provides a range of visual components, called Block Kit, that can be used in messages. These blocks can be used to lay out complex information in an understandable way.

      Define a single block

      Each block is represented in our APIs as a JSON object. Here's an example of a section block:

      {
        "type": "section",
        "text": {
          "type": "mrkdwn",
          "text": "New Paid Time Off request from <example.com|Fred Enriquez>\n\n<https://example.com|View request>"
        }
      }
      

      Preview in Block Kit Builder

      Every block contains a type field — specifying which of the available blocks to use — along with other fields that describe the content of the block.

      Block Kit Builder is a visual prototyping sandbox that will let you choose from, configure, and preview all the available blocks.

      If you want to skip the builder, the block reference guide contains the specifications of every block, and the JSON fields required for each of them.

      Stacking multiple blocks

      Individual blocks can be stacked together to create complex visual layouts.

      When you've chosen each of the blocks you want in your layout, place each of them in an array, in visual order, like this:

      [
      	{
      		"type": "header",
      		"text": {
      			"type": "plain_text",
      			"text": "New request"
      		}
      	},
      	{
      		"type": "section",
      		"fields": [
      			{
      				"type": "mrkdwn",
      				"text": "*Type:*\nPaid Time Off"
      			},
      			{
      				"type": "mrkdwn",
      				"text": "*Created by:*\n<example.com|Fred Enriquez>"
      			}
      		]
      	},
      	{
      		"type": "section",
      		"fields": [
      			{
      				"type": "mrkdwn",
      				"text": "*When:*\nAug 10 - Aug 13"
      			}
      		]
      	},
      	{
      		"type": "section",
      		"text": {
      			"type": "mrkdwn",
      			"text": "<https://example.com|View request>"
      		}
      	}
      ]
      

      Preview in Block Kit Builder

      Block Kit Builder will allow you to drag, drop, and rearrange blocks to design and preview Block Kit layouts.

      Alternatively you can use the block reference guide to manually generate a complete blocks array, like the one shown above.

      Add blocks to your message payload

      Blocks are added to messages by adding a blocks array to the message payload, like this:

      {
        "channel": "C123ABC456",
        "text": "New Paid Time Off request from Fred Enriquez",
        "blocks": [
      		{
      			"type": "header",
      			"text": {
      				"type": "plain_text",
      				"text": "New request"
      			}
      		},
      		{
      			"type": "section",
      			"fields": [
      				{
      					"type": "mrkdwn",
      					"text": "*Type:*\nPaid Time Off"
      				},
      				{
      					"type": "mrkdwn",
      					"text": "*Created by:*\n<example.com|Fred Enriquez>"
      				}
      			]
      		},
      		{
      			"type": "section",
      			"fields": [
      				{
      					"type": "mrkdwn",
      					"text": "*When:*\nAug 10 - Aug 13"
      				}
      			]
      		},
      		{
      			"type": "section",
      			"text": {
      				"type": "mrkdwn",
      				"text": "<https://example.com|View request>"
      			}
      		}
      	]
      }
      

      When you're using blocks in your message payload, the top-level text field becomes a fallback message displayed in notifications. Blocks should define everything else about the desired visible message.

    • Add interactive blocks to your message

      You can take Block Kit layouts further by adding interactive components like buttons, select menus, date pickers, and more.

      Interactive components allow your app to retrieve, manipulate, update, and return data to external services in response to user action. All from the comfort of Slack.

      Adding interactive components is no different from adding any other block. Let's add a couple of buttons to an actions block in a message payload:

      {
        	"channel": "C123ABC456",
        	"text": "New Paid Time Off request from Fred Enriquez",
      	"blocks": [
      		{
      			"type": "header",
      			"text": {
      				"type": "plain_text",
      				"text": "New request",
      				"emoji": true
      			}
      		},
      		{
      			"type": "section",
      			"fields": [
      				{
      					"type": "mrkdwn",
      					"text": "*Type:*\nPaid Time Off"
      				},
      				{
      					"type": "mrkdwn",
      					"text": "*Created by:*\n<example.com|Fred Enriquez>"
      				}
      			]
      		},
      		{
      			"type": "section",
      			"fields": [
      				{
      					"type": "mrkdwn",
      					"text": "*When:*\nAug 10 - Aug 13"
      				}
      			]
      		},
      		{
      			"type": "actions",
      			"elements": [
      				{
      					"type": "button",
      					"text": {
      						"type": "plain_text",
      						"emoji": true,
      						"text": "Approve"
      					},
      					"style": "primary",
      					"value": "click_me_123"
      				},
      				{
      					"type": "button",
      					"text": {
      						"type": "plain_text",
      						"emoji": true,
      						"text": "Reject"
      					},
      					"style": "danger",
      					"value": "click_me_123"
      				}
      			]
      		}
      	]
      }
      

      Preview

      You could replace these buttons with any of the available interactive components. You can also add a button as a section block's accessory element, rather than the actions block used above.

      Browse the interactive components to see a full list of what's available, or try the Block Kit Builder tool to visually prototype a layout with interactive components.

      When the message payload you've constructed here is published to Slack, people will be able to click on the buttons included in the layout. When that happens, your app will be sent interactive payloads for processing and response.

      We'll show you how these interactive payloads work, and how to configure your app to receive them, in a later step.

    Step complete!

    Step 3Learn how to use the Web API

    The Web API is the core of every Slack app's functionality. In the next section, we'll use it to access the API for publishing messages.

    Before we do that, familiarize yourself with the process for actually using the API. We recommend using Bolt to save yourself a lot of time and effort.

    When you're done

    After finishing this section, you should be familiar with the basics of using the Web API in an app.

    • Using the Slack Web API with Bolt

      All flavors of Bolt use a similar client interface, with API parameters passed as arguments.

      Here's a basic Bolt example that calls chat.postMessage:

      Calling chat.postMessage
      Java
      JavaScript
      Python
      Java
      import com.slack.api.bolt.App; import com.slack.api.bolt.AppConfig; import com.slack.api.bolt.jetty.SlackAppServer; import com.slack.api.methods.SlackApiException; import java.io.IOException; public class ChatPostMessage { public static void main(String[] args) throws Exception { var config = new AppConfig(); config.setSingleTeamBotToken(System.getenv("SLACK_BOT_TOKEN")); config.setSigningSecret(System.getenv("SLACK_SIGNING_SECRET")); var app = new App(config); // `new App()` does the same app.message("hello", (req, ctx) -> { var logger = ctx.logger; try { var event = req.getEvent(); // Call the chat.postMessage method using the built-in WebClient var result = ctx.client().chatPostMessage(r -> r // The token you used to initialize your app is stored in the `context` object .token(ctx.getBotToken()) // Payload message should be posted in the channel where original message was heard .channel(event.getChannel()) .text("world") ); logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } return ctx.ack(); }); var server = new SlackAppServer(app); server.start(); } }
      JavaScript
      Code to initialize Bolt app
      // Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
      // ID of the channel you want to send the message to const channelId = "C12345"; try { // Call the chat.postMessage method using the WebClient const result = await client.chat.postMessage({ channel: channelId, text: "Hello world" }); console.log(result); } catch (error) { console.error(error); }
      Python
      Code to initialize Bolt app
      import logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
      # ID of the channel you want to send the message to channel_id = "C12345" try: # Call the chat.postMessage method using the WebClient result = client.chat_postMessage( channel=channel_id, text="Hello world" ) logger.info(result) except SlackApiError as e: logger.error(f"Error posting message: {e}")

      If you're curious, you can read more about how Bolt can be used to call the Web API—just consult the relevant Bolt for JavaScript, Bolt for Python, or Bolt for Java docs.

    • AlternativeUse the Web API manually

      Not using Bolt? Read how to manually build an app that can interact with the Web API.

    Step complete!

    Step 4Publish your interactive notification

    Putting everything you've learned so far together, you're ready to make the API calls that will publish a notification as a message.

    Because you requested chat:write and chat:write.public permissions earlier, your app will be able to publish to any Slack conversation it is a member of or any public channel, at any time. Notification messages should typically be sent in reaction to some event on a service outside of Slack.

    To publish your message, we'll use the chat.postMessage Web API method.

    When you're done

    After finishing this step, your app will have published an interactive notification to a Slack conversation of your choice.

    • Find a place to publish your message

      When an app publishes a message, the app needs to know which Slack conversation the message should be added to.

      In order to find a valid Slack conversation ID, we'll use the conversations.list API method. This API will return a list of all public channels in the workspace your app is installed to. You'll need the channels:read permission granted to your app.

      Within that list, we'll be able to find a specific id of the conversation that we want to access. Here's an example API call:

      Finding a conversation
      Java
      JavaScript
      Python
      HTTP
      Java
      import com.slack.api.Slack; import com.slack.api.methods.SlackApiException; import com.slack.api.model.Conversation; import org.slf4j.LoggerFactory; import java.io.IOException; public class FindingConversation { /** * Find conversation ID using the conversations.list method */ static void findConversation(String name) { // you can get this instance via ctx.client() in a Bolt app var client = Slack.getInstance().methods(); var logger = LoggerFactory.getLogger("my-awesome-slack-app"); try { // Call the conversations.list method using the built-in WebClient var result = client.conversationsList(r -> r // The token you used to initialize your app .token(System.getenv("SLACK_BOT_TOKEN")) ); for (Conversation channel : result.getChannels()) { if (channel.getName().equals(name)) { var conversationId = channel.getId(); // Print result logger.info("Found conversation ID: {}", conversationId); // Break from for loop break; } } } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } } public static void main(String[] args) throws Exception { // Find conversation with a specified channel `name` findConversation("tester-channel"); } }
      JavaScript
      Code to initialize Bolt app
      // Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
      // Find conversation ID using the conversations.list method async function findConversation(name) { try { // Call the conversations.list method using the built-in WebClient const result = await app.client.conversations.list({ // The token you used to initialize your app token: "xoxb-your-token" }); for (const channel of result.channels) { if (channel.name === name) { conversationId = channel.id; // Print result console.log("Found conversation ID: " + conversationId); // Break from for loop break; } } } catch (error) { console.error(error); } } // Find conversation with a specified channel `name` findConversation("tester-channel");
      Python
      Code to initialize Bolt app
      import logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
      channel_name = "needle" conversation_id = None try: # Call the conversations.list method using the WebClient for result in client.conversations_list(): if conversation_id is not None: break for channel in result["channels"]: if channel["name"] == channel_name: conversation_id = channel["id"] #Print result print(f"Found conversation ID: {conversation_id}") break except SlackApiError as e: print(f"Error: {e}")
      HTTP
      GET https://slack.com/api/conversations.list Authorization: Bearer xoxb-your-token

      You'll get back a JSON object, with a channels array containing all the public channels that your app can see. You can find your channel by looking for the name in each object.

      When you've found the matching channel, make note of the id value, as you'll need it for certain API calls.

      If your app implements shortcuts, slash commands, or uses the Events API, your app will see conversation ids in request payloads sent by those features.

      In those cases, your app can dynamically respond using the payload data to identify the relevant conversation, rather than needing to use the conversations.list method described above.

    • Publish your message with Bolt

      If you're using Bolt, call chat.postMessage as below, making sure to include your blocks array in the message payload:

      Calling chat.postMessage
      Java
      JavaScript
      Python
      Java
      import com.slack.api.bolt.App; import com.slack.api.bolt.AppConfig; import com.slack.api.bolt.jetty.SlackAppServer; import com.slack.api.methods.SlackApiException; import java.io.IOException; public class ChatPostMessage { public static void main(String[] args) throws Exception { var config = new AppConfig(); config.setSingleTeamBotToken(System.getenv("SLACK_BOT_TOKEN")); config.setSigningSecret(System.getenv("SLACK_SIGNING_SECRET")); var app = new App(config); // `new App()` does the same app.message("hello", (req, ctx) -> { var logger = ctx.logger; try { var event = req.getEvent(); // Call the chat.postMessage method using the built-in WebClient var result = ctx.client().chatPostMessage(r -> r // The token you used to initialize your app is stored in the `context` object .token(ctx.getBotToken()) // Payload message should be posted in the channel where original message was heard .channel(event.getChannel()) .text("world") ); logger.info("result: {}", result); } catch (IOException | SlackApiException e) { logger.error("error: {}", e.getMessage(), e); } return ctx.ack(); }); var server = new SlackAppServer(app); server.start(); } }
      JavaScript
      Code to initialize Bolt app
      // Require the Node Slack SDK package (github.com/slackapi/node-slack-sdk) const { WebClient, LogLevel } = require("@slack/web-api"); // WebClient instantiates a client that can call API methods // When using Bolt, you can use either `app.client` or the `client` passed to listeners. const client = new WebClient("xoxb-your-token", { // LogLevel can be imported and used to make debugging simpler logLevel: LogLevel.DEBUG });
      // ID of the channel you want to send the message to const channelId = "C12345"; try { // Call the chat.postMessage method using the WebClient const result = await client.chat.postMessage({ channel: channelId, text: "Hello world" }); console.log(result); } catch (error) { console.error(error); }
      Python
      Code to initialize Bolt app
      import logging import os # Import WebClient from Python SDK (github.com/slackapi/python-slack-sdk) from slack_sdk import WebClient from slack_sdk.errors import SlackApiError # WebClient instantiates a client that can call API methods # When using Bolt, you can use either `app.client` or the `client` passed to listeners. client = WebClient(token=os.environ.get("SLACK_BOT_TOKEN")) logger = logging.getLogger(__name__)
      # ID of the channel you want to send the message to channel_id = "C12345" try: # Call the chat.postMessage method using the WebClient result = client.chat_postMessage( channel=channel_id, text="Hello world" ) logger.info(result) except SlackApiError as e: logger.error(f"Error posting message: {e}")
    • AlternativePublish your message manually

      If you're not using Bolt, you'll need to manually create an API call to chat.postMessage.

      Read our detailed guide to sending messages if you want to learn more.

    Step complete!

    Step 5Prepare your app for interactions

    Your interactive notification message should now be sitting in the chosen conversation, with some buttons awaiting interaction.

    When one of those buttons are used, Slack will send an interaction payload to your app. You'll use the information in that payload to spur some relevant action in your app.

    When you're done

    After finishing this step, your app will be able to handle interactions with published notifications.

    • A brief overview of interaction flow

      Every interaction between a user and an app exists as part of a flow, a series of events that gets something done:

      A user triggers an app interaction by using an interactive Block Kit component or a shortcut.

      An interaction payload is sent to the app, which correctly receives and processes it.

      Using the context in the interaction payload, the app responds to the interaction.

      In the next few steps, we'll get your app ready to handle this flow.

    • Configure a Request URL

      Interaction payloads are sent to your app's configured Request URL. To add a Request URL to your app:

      1. Head over to app config page for your app.
      2. Navigate to the Interactivity & Shortcuts page.
      3. If you haven't already, switch on the Interactivity toggle.
      4. Add your Request URL.
      5. Click Save Changes at the bottom of the page.

      If you're using Socket Mode, you won't need to configure a Request URL. Interaction payloads will instead be sent to your WebSocket URL, and will be wrapped in some Socket Mode specific metadata.

    • Receive interaction payloads with Bolt

      If you're using the Bolt framework, the process of receiving and processing interaction payloads is handled for you. All you need to do is create a listener for each block_id or action_id created by your app:

      Capturing block actions
      Java
      JavaScript
      Python
      Java
      // This listener will be called every time an interactive component with the `action_id` "approve_button" is triggered // `block_id` is disregarded in this case app.blockAction("approve_button", (req, ctx) -> { String value = req.getPayload().getActions().get(0).getValue(); // "button's value" // Do something in response return ctx.ack(); });
      JavaScript
      // This listener will be called every time an interactive component with the `action_id` "approve_button" is triggered // `block_id` is disregarded in this case app.action('approve_button', async ({ ack, say }) => { await ack(); // Do something in response }); // This listener will only be called when the `action_id` matches 'select_user' AND the `block_id` matches 'assign_ticket' app.action({ action_id: 'select_user', block_id: 'assign_ticket' }, async ({ body, action, ack, client }) => { await ack(); // Do something in response });
      Python
      # This listener will be called every time an interactive component with the `action_id` "approve_button" is triggered # `block_id` is disregarded in this case @app.action("approve_button") def some_action_response(ack): ack() # Do something in response # This listener will only be called when the `action_id` matches 'select_user' AND the `block_id` matches 'assign_ticket' @app.action({ "block_id": "assign_ticket", "action_id": "select_user" }) def some_other_action_response(ack, body, client): ack() # Do something in response

      Your app has access to all the contextual info about the interaction that's included in the payload. Your app can use as much or as little of the info as needed to generate a response.

      You can view a full list of the fields included in our block_actions payload reference.

      Send an acknowledgment response

      All apps must acknowledge the receipt of an interaction payload within 3 seconds. To enable this acknowledgment response, Bolt action listeners are passed a callable ack() function (in Bolt for Java this function is ctx.ack()).

      This ack() function requires no arguments. We recommend calling ack() immediately, before any other processing, since you only have 3 seconds to respond.

    • AlternativeReceive interaction payloads manually

      Not using Bolt? Read how to manually build an app that can receive and process interaction payloads.

    • OptionalRespond to an interaction

      If you've reached this step, your app should be able to successfully handle user interactions. Now you need to decide what your app will do in response. Send a message to the user, pop a modal, update data on an external service, or lots of other possibilities.

      Read our guide to responding to user interactions to help you decide what to do.

    Step complete!

    Was this page helpful?