AWS Machine Learning Blog

Use Block Kit when integrating Amazon Lex bots with Slack

If you’re integrating your Amazon Lex chatbots with Slack, chances are you’ll come across Block Kit. Block Kit is a UI framework for Slack apps. Like response cards, Block Kit can help simplify interactions with your users. It offers flexibility to format your bot messages with blocks, buttons, check boxes, date pickers, time pickers, select menus, and more.

Amazon Lex provides channel integration with messaging platforms such as Slack, Facebook, and Twilio. For instructions on integrating with Slack, see Integrating an Amazon Lex Bot with Slack. You can also update the interactivity and shortcuts feature with the request URL that Amazon Lex generated. If you want to use Block Kit and other Slack native components, you need a custom endpoint for the request URL.

This post describes a solution architecture with a custom endpoint and shows how to use Block Kit with your Amazon Lex bot. It also provides an AWS Serverless Application Model (AWS SAM) template implementing the architecture.

Solution overview

In the proposed architecture, we use Amazon API Gateway for the custom endpoint and an AWS Lambda function to process the events. We also introduce an Amazon Simple Queue Service (Amazon SQS) queue to invoke the Lambda function asynchronously. The rest of the architecture includes an Amazon Lex bot and another Lambda function used for initialization, validation, and fulfillment. We use Python for the provided code examples.

The following diagram illustrates the solution architecture.

Use Slack Block Kit with an Amazon Lex bot to post messages

You can use Block Kit to format messages you configured at build time within the Lambda function associated with an intent. The following example uses blocks to display available flowers to users.

Each time you want to display a message with blocks, the following steps are required:

  1. Build the block. Block Kit Builder helps you visually format your messages.
  2. Check whether the request originated from Slack before you post the block. This allows you to deploy your bots on multiple platforms without major changes.
  3. Use the chat_postMessage operation from the Slack WebClient to post them in Slack. You can use the following operation to post both text and blocks to Slack:
def postInSlack(user_id, message, messageType='Plaintext', bot_token=slacksecret['SLACK_BOT_TOKEN']):
    try:
        # Call the chat.postMessage method using the WebClient
        if (messageType == 'blocks'):
            result = slackClient.chat_postMessage(
            channel=user_id, token=bot_token, blocks=message
        )

        else:
            result = slackClient.chat_postMessage(
            channel=user_id, token=bot_token, text=message
        )

    except SlackApiError as e:
        logger.error(f"Error posting message: {e}")

To illustrate those steps with the OrderFlowers bot, we show you how to use a date picker from Block Kit to re-prompt users for the pick-up date.

  1. First, you build the block in the format Slack expects:
    def get_pickup_date_block():
    	responseBlock = [
    		{
    			"type": "section",
    			"text": {
    			    "type": "mrkdwn",
                    "text": "Pick a date to pick up your flower"
    			},
    			"accessory": {
    				"type": "datepicker",
    				"action_id": "datepicker123",
    				"initial_date": f'{datetime.date.today()}',
    				"placeholder": {
    					"type": "plain_text",
    					"text": "Select a date"
    				}
    			}
    		}
    ]
  2. Then, you modify the validation code hook as follows. This checks if the request originated from Slack using the channel-type request attribute.
    if source == 'DialogCodeHook':
        slots = helper.get_slots(intent_request)
        validation_result = validate_order_flowers(flower_type, date, pickup_time)
        if not validation_result['isValid']:
          		slots[validation_result['violatedSlot']] = None
                
            	#Check if request from slack 
    
                if intent_request['requestAttributes'] and 'x-amz-lex:channel-type' in intent_request['requestAttributes'] and intent_request['requestAttributes']['x-amz-lex:channel-type'] == 'Slack':
                	    blocks = []
                        channel_id = intent_request['userId'].split(':')[2]
    
  3. If the violated slot is PickupDate, you post the block you defined earlier to Slack. Then, you ask Amazon Lex to elicit the slot with the returned validation message:
    if validation_result['violatedslot'] == 'PickupDate':
        blocks = get_pickup_date_block()
                         
    helper.postInSlack (channel_id, blocks, 'blocks')
    return helper.elicit_slot( intent_request['sessionAttributes'], intent_request['currentIntent']['name'], slots, validation_result['violatedSlot'], validation_result['message'])
    

Outside of Slack, the user only receives the validation result message.

In Slack, the user receives both the pick-up date block and the validation result message.

You can use this approach to complement messages that you had configured at build time with Block Kit.

User interactions

Now that you know how to use blocks to post your bot messages, let’s go over how you handle users’ interactions with the blocks.

When a user interacts with an action block element, the following steps take place:

  1. Slack sends an HTTP request to API Gateway.
  2. API Gateway forwards the request to Amazon SQS.
  3. Amazon SQS receives the transformed request as a message, and invokes the Lambda function that processes the request.

The following diagram illustrates the interaction flow.

Let’s take a closer look at what happens at each step.

Slack sends an HTTP request to API Gateway

When a user chooses an action block element, Slack sends an HTTP post with the event details to the endpoint configured as request URL. The endpoint should reply to Slack with an HTTP 2xx response within 3 seconds. If not, Slack resends the same event. We decouple the ingestion and processing of events by using an Amazon SQS queue between API Gateway and the processing Lambda function. The queue allows you to reply to events with HTTP 200, queue them, and asynchronously process them. This prevents unnecessary retry events from flooding the custom endpoint.

API Gateway forwards the request to Amazon SQS

When API Gateway receives an event from Slack, it uses an integration request-mapping template to transform the request to the format Amazon SQS is expecting. Then it forwards the request to Amazon SQS.

Amazon SQS receives and processes the transformed request

When Amazon SQS receives the message, it initiates the process Lambda function and returns the 200 HTTP response to API Gateway that, in turn, returns the HTTP response to Slack.

Process requests

The Lambda function completes the following steps:

  1. Verify that the received request is from Slack.
  2. Forward the text value associated to the event to Amazon Lex.
  3. Post the Amazon Lex response to Slack.

In this section, we discuss each step in more detail.

Verify that the received request is from Slack

Use the signature module from slack_sdk to verify the requests. You can save and retrieve your signing secret from AWS Secrets Manager. For Slack’s recommendation on request verification, see Verifying requests from Slack.

Forward the text value associated to the event to Amazon Lex

If the request is from Slack, the Lambda function extracts the text value associated with the action type. Then it forwards the user input to Amazon Lex. See the following code:

actions = payload["actions"]
team_id = payload["team"]["id"]
user_id = payload["user"]["id"]
action_type = actions[0]["type"]
if action_type == "button":    
       forwardToLex = actions[0]["value"]
elif action_type == 'datepicker':
       forwardToLex = actions[0]['selected_date']
else:
       forwardToLex = "None"
forward_to_Lex(team_id, user_id, forwardToLex)

We use the Amazon Lex client post_text operation to forward the text to Amazon Lex. You can also store and retrieve the bot’s name, bot’s alias, and the channel ID from Secrets Manager. See the following code:

#Post event received from Slack to Lex and post Lex reply to #Slack
def forward_to_Lex(team_id, user_id, forwardToLex):
    response = lexClient.post_text(
    botName=slacksecret['BOT_NAME'],
    botAlias=slacksecret['BOT_ALIAS'],
    userId=slacksecret['LEX_SLACK_CHANNEL_ID']+":"+ team_id+ ":" + user_id,
    inputText=forwardToLex
    ) 

Post the Amazon Lex response to Slack

Finally, we post the message from Amazon Lex to Slack:

postInSlack(user_id, response['message'])

The following screenshot shows the response on Slack.

From the user’s perspectives, the experience is the following:

  1. The bot re-prompts the user for the pick-up date with a date picker.
  2. The user selects a date.
  3. The bot prompts the user for the pick-up time.

The messages that use Block Kit are seamlessly integrated to the original conversation flow with the Amazon Lex bot.

Walkthrough

In this part of the post, we walk through the deployment and configuration of the components you need to use Block Kit. We go over the following steps:

  1. Launch the prerequisite resources.
  2. Update the Slack request URL with the deployed API Gateway endpoint.
  3. Gather information for Secrets Manager.
  4. Populate the secret value.
  5. Update the Lambda function for Amazon Lex fulfillment initialization and validation.
  6. Update the listener Lambda function.
  7. Test the integration.

Prerequisites

For this walkthrough, you need the following:

Integrate Amazon Lex and Slack with a custom request URL

To create the resources, complete the following steps:

  1. Clone the repository https://github.com/aws-samples/amazon-lex-slack-block-kit:
git clone https://github.com/aws-samples/amazon-lex-slack-block-kit.git
  1. Build the application and run the guided deploy command:
cd amazon-lex-slack-block-kit
sam build
sam deploy --guided

These steps deploy an AWS CloudFormation stack that launches the following resources:

  • An API Gateway endpoint integrated with an SQS queue
  • A Lambda function to listen to requests from Slack
  • A Lambda function for Amazon Lex fulfillment, initialization, and validation hooks
  • AWS Identity and Access Management (IAM) roles associated to the API and the Lambda functions
  • A Lambda layer with slack_sdk, urllib3, and common operations used by the two Lambda functions
  • A secret in Secrets Manager with the secret keys our code uses

Update the Slack request URL

To update the Slack request URL, complete the following steps:

  1. On the AWS CloudFormation console, navigate to the stack Outputs tab and copy the ListenSlackApi endpoint URL.
  2. Sign in to the Slack API console.
  3. Choose the app you integrated with Amazon Lex.
  4. Update the Interactivity & Shortcuts feature by replacing the value for Request URL with the ListenSlackApi endpoint URL.
  5. Choose Save Changes.

Gather information for Secrets Manager

To gather information for Secrets Manager, complete the following steps:

  1. On the Slack API console, under Settings, choose Basic Information.
  2. Note down the value for Signing Secret.
  3. Under Features, choose OAuth & Permissions.
  4. Note down the value for Bot User OAuth Token.
  5. On the Amazon Lex console, note the following:
    • Your bot’s name
    • Your bot’s alias
    • The last part of the two callback URLs that Amazon Lex generated when you created your Slack channel (for example, https://channels.lex.us-east-1.amazonaws.com/slack/webhook/value-to-record).

Populate the secret value

To populate the secret value, complete the following steps:

  1. On the Secrets Manager console, from the list of secrets, choose SLACK_LEX_BLOCK_KIT.
  2. Choose Retrieve secret value.
  3. Choose Edit.
  4. Replace the secret values as follows:
    1. SLACK_SIGNING_SECRET – The signing secret from Slack.
    2. SLACK_BOT_TOKEN – The bot user OAuth token from Slack.
    3. BOT_NAME – Your Amazon Lex bot’s name.
    4. BOT_ALIAS – Your Amazon Lex bot’s alias name.
    5. LEX_SLACK_CHANNEL_ID – The value you recorded from the callback URLs.
  5. Choose Save.

Update the Lambda fulfillment function and Lambda initialization and validation for your Amazon Lex bot

If you’re using the OrderFlowers bot, follow the instructions in Step 4: Add the Lambda Function as Code Hook (Console) to add the Lambda function amazon-lex-slack-block-kit-OrderFlowerFunction as code hooks for fulfillment, initialization, and validation.

If you’re not using the OrderFlowers bot, use the Lambda layer slack-lex-block that the stack created if your runtime is Python version 3.6 and later. The layer includes an operation postInSlack to post your blocks:

helper.postInSlack (channel_id, blocks, 'blocks')

You can use Slack Block Kit Builder to build your blocks.

Update the listener Lambda function

If you’re using the OrderFlowers bot, move to the next step to test the integration.

If you’re not using the OrderFlowers bot, update the Lambda function starting with amazon-lex-slack-block-kit-ListenFunction to process the actions your blocks used.

Test the integration

To test the integration, complete the following steps:

  1. Go back to the Slack team where you installed your application.
  2. In the navigation pane, in the Direct Messages section, choose your bot.

If you don’t see your bot, choose the plus icon (+) next to Direct Messages to search for it.

  1. Engage in a conversation with your Slack application.

Your bot now prompts you with the blocks you configured, as shown in the following example conversation.

Clean up

To avoid incurring future charges, delete the CloudFormation stack via the AWS CloudFormation console or the AWS Command Line Interface (AWS CLI):

aws cloudformation delete-stack --stack-name amazon-lex-slack-block-kit

You also need to delete the Amazon Lex bot resources that you created, the Amazon CloudWatch logs, and the Lambda layer that was created by the stack.

Conclusion

In this post, we showed how to use Block Kit to format Amazon Lex messages within Slack. We provided code examples to post blocks to Slack, listen to events from users’ interactions with the blocks’ elements, and process those events. We also walked you through deploying and configuring the necessary components to use Block Kit. Try the code examples and adapt them for your use case as you see fit.


About the Author

Anne Martine Augustin is an Application Consultant for AWS Professional Services based in Houston, TX. She is passionate about helping customers architect and build modern applications that accelerate their business outcomes. In her spare time, Martine enjoys spending time with friends and family, listening to audio books, and trying new foods.