> ## Documentation Index
> Fetch the complete documentation index at: https://developer.box.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Configure Slack

export const ProgressBar = ({pages = [], ...props}) => {
  const [currentStep, setCurrentStep] = useState(0);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const checkDarkMode = () => {
      const isDark = document.documentElement.classList.contains('dark');
      console.log('ProgressBar - isDarkMode:', isDark);
      setIsDarkMode(isDark);
    };
    checkDarkMode();
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ['class']
    });
    return () => {
      observer.disconnect();
    };
  }, []);
  useEffect(() => {
    if (pages.length > 0) {
      const currentPath = window.location.pathname;
      const stepIndex = pages.findIndex(page => {
        const pagePath = page.startsWith('/') ? page : `/${page}`;
        return currentPath.endsWith(pagePath) || currentPath.includes(pagePath);
      });
      if (stepIndex !== -1) {
        setCurrentStep(stepIndex + 1);
      }
    }
  }, [pages]);
  if (!pages || pages.length === 0) {
    return null;
  }
  const step = currentStep;
  const total = pages.length;
  console.log('ProgressBar - Rendering with isDarkMode:', isDarkMode);
  const progressBarContainerStyle = {
    width: '100%',
    marginBottom: '32px',
    display: 'flex',
    alignItems: 'center',
    gap: '16px'
  };
  const stepsContainerStyle = {
    display: 'flex',
    alignItems: 'center',
    gap: '8px',
    flexShrink: 0
  };
  const progressBarTrackStyle = {
    flex: 1,
    height: '22px',
    backgroundColor: 'rgba(169, 210, 244, 0.06)',
    border: isDarkMode ? '1px solid rgba(230, 241, 247, 0.67)' : '1px solid #e3ecf3',
    borderRadius: '4px',
    overflow: 'hidden',
    position: 'relative'
  };
  const progressBarFillStyle = {
    height: '100%',
    backgroundColor: 'rgba(113, 192, 248, 0.23)',
    width: `${step / total * 100}%`,
    transition: 'width 0.3s ease'
  };
  const getStepStyle = (stepNumber, isActive) => {
    if (isDarkMode) {
      return {
        width: '22px',
        height: '22px',
        borderRadius: '4px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: '600',
        position: 'relative',
        zIndex: 1,
        transition: 'all 0.3s ease',
        backgroundColor: isActive ? 'rgba(113, 192, 248, 0.23)' : 'transparent',
        color: isActive ? '#60a5fa' : '#a0aec0',
        border: isActive ? '1px solid #e3ecf3' : '1px solid #e0e6eb',
        cursor: 'pointer',
        textDecoration: 'none'
      };
    }
    if (isActive) {
      return {
        width: '22px',
        height: '22px',
        borderRadius: '4px',
        display: 'flex',
        alignItems: 'center',
        justifyContent: 'center',
        fontSize: '12px',
        fontWeight: '600',
        position: 'relative',
        zIndex: 1,
        transition: 'all 0.3s ease',
        backgroundColor: 'rgba(169, 210, 244, 0.32)',
        color: '#374151',
        border: '1px solid #e1eef8',
        cursor: 'pointer',
        textDecoration: 'none'
      };
    }
    return {
      width: '22px',
      height: '22px',
      borderRadius: '4px',
      display: 'flex',
      alignItems: 'center',
      justifyContent: 'center',
      fontSize: '12px',
      fontWeight: '600',
      position: 'relative',
      zIndex: 1,
      transition: 'all 0.3s ease',
      backgroundColor: '#fbfbfb',
      color: '#9ca3af',
      border: '1px solid #e3ecf3',
      cursor: 'pointer',
      textDecoration: 'none'
    };
  };
  return <div style={progressBarContainerStyle} {...props}>
      <div style={stepsContainerStyle}>
        {Array.from({
    length: total
  }, (_, index) => {
    const stepNumber = index + 1;
    const pageIndex = index;
    const pagePath = pages[pageIndex];
    const fullPath = pagePath.startsWith('/') ? pagePath : `/${pagePath}`;
    const isActive = stepNumber === step;
    return <a key={stepNumber} href={fullPath} style={getStepStyle(stepNumber, isActive)}>
              {stepNumber}
            </a>;
  })}
      </div>
      <div style={progressBarTrackStyle}>
        <div style={progressBarFillStyle}></div>
      </div>
    </div>;
};

export const ChoiceDebug = ({option}) => {
  const [currentValue, setCurrentValue] = useState(null);
  const [allState, setAllState] = useState({});
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const updateState = () => {
      if (window.choiceStateManager) {
        setCurrentValue(window.choiceStateManager.getValue(option));
        setAllState(window.choiceStateManager.getState());
      }
    };
    updateState();
    const unsubscribe = window.listenToChoice?.(option, updateState) || (() => {});
    const handleGlobalUpdate = () => updateState();
    window.addEventListener("choiceStateUpdate", handleGlobalUpdate);
    return () => {
      unsubscribe();
      window.removeEventListener("choiceStateUpdate", handleGlobalUpdate);
    };
  }, [option]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  return <div style={{
    padding: "10px",
    backgroundColor: isDarkMode ? "#2d3748" : "#f5f5f5",
    border: isDarkMode ? "1px solid #4a5568" : "1px solid #ddd",
    borderRadius: "4px",
    fontSize: "12px",
    fontFamily: "monospace",
    marginTop: "20px",
    color: isDarkMode ? "#e2e8f0" : "inherit"
  }}>
      <strong>Choice Debug:</strong>
      <br />
      Option: {option}
      <br />
      Current Value: {currentValue || "undefined"}
      <br />
      All State: {JSON.stringify(allState, null, 2)}
    </div>;
};

export const Observe = ({option, value, children, ...props}) => {
  const [shouldShow, setShouldShow] = useState(false);
  useEffect(() => {
    const updateVisibility = () => {
      const matches = window.matchesChoiceValues?.(option, value) || false;
      setShouldShow(matches);
    };
    updateVisibility();
    const unsubscribe = window.listenToChoice?.(option, updateVisibility) || (() => {});
    return unsubscribe;
  }, [option, value]);
  if (!shouldShow) {
    return null;
  }
  return <div {...props}>{children}</div>;
};

export const Trigger = ({option, value, children, ...props}) => {
  const handleClick = () => {
    window.triggerChoice?.(option, value);
  };
  return <div onClick={handleClick} style={{
    cursor: "pointer"
  }} {...props}>
      {children}
    </div>;
};

export const Grid = ({columns = 2, compact = false, children, ...props}) => {
  const gridStyles = {
    display: "grid",
    gridTemplateColumns: `repeat(${columns}, 1fr)`,
    gap: compact ? "8px" : "16px",
    marginBottom: compact ? "10px" : "20px"
  };
  return <div style={gridStyles} {...props}>
      {children}
    </div>;
};

export const Choice = ({option, value, color = "", unset = false, lazy = false, children, ...props}) => {
  const [shouldShow, setShouldShow] = useState(false);
  const [hasEverShown, setHasEverShown] = useState(false);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const updateVisibility = () => {
      const hasOptionValue = window.hasChoiceValue?.(option) || false;
      const matchesValue = window.matchesChoiceValues?.(option, value) || false;
      let show = false;
      if (unset && !hasOptionValue) {
        show = true;
      } else if (!unset && matchesValue) {
        show = true;
      }
      setShouldShow(show);
      if (show && !hasEverShown) {
        setHasEverShown(true);
      }
    };
    updateVisibility();
    const unsubscribe = window.listenToChoice?.(option, updateVisibility) || (() => {});
    return unsubscribe;
  }, [option, value, unset, hasEverShown]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  const getColorStyles = () => {
    const baseStyles = {
      border: isDarkMode ? "1px dashed #4a5568" : "1px dashed #e1e5e9",
      padding: "20px",
      marginBottom: "20px",
      borderRadius: "8px",
      backgroundColor: isDarkMode ? "#1a202c" : "#ffffff"
    };
    const colorMap = {
      green: {
        light: {
          backgroundColor: "#d4edda",
          borderColor: "#28a745"
        },
        dark: {
          backgroundColor: "#1a3a2a",
          borderColor: "#66bb6a"
        }
      },
      red: {
        light: {
          backgroundColor: "#f8d7da",
          borderColor: "#dc3545"
        },
        dark: {
          backgroundColor: "#3a1a1a",
          borderColor: "#ef5350"
        }
      },
      blue: {
        light: {
          backgroundColor: "#d1ecf1",
          borderColor: "#0c5460"
        },
        dark: {
          backgroundColor: "#1a2a3a",
          borderColor: "#42a5f5"
        }
      },
      none: {
        backgroundColor: "transparent",
        padding: "0",
        margin: "0",
        border: "none"
      }
    };
    const colorStyles = color !== "none" ? colorMap[color]?.[isDarkMode ? "dark" : "light"] || ({}) : colorMap.none;
    return {
      ...baseStyles,
      ...colorStyles
    };
  };
  if (lazy && !hasEverShown && !shouldShow) {
    return null;
  }
  return <div style={{
    ...getColorStyles(),
    display: shouldShow ? "block" : "none"
  }} className="choice-content" {...props}>
      {children}
    </div>;
};

export const Choose = ({option, value, color = "", children, ...props}) => {
  const [isSelected, setIsSelected] = useState(false);
  const [hasOptionTriggered, setHasOptionTriggered] = useState(false);
  const [isDarkMode, setIsDarkMode] = useState(false);
  useEffect(() => {
    const currentValue = window.getChoiceValue?.(option);
    const optionTriggered = window.hasChoiceValue?.(option) || false;
    setIsSelected(currentValue === value);
    setHasOptionTriggered(optionTriggered);
    const unsubscribe = window.listenToChoice?.(option, newValue => {
      setIsSelected(newValue === value);
      setHasOptionTriggered(true);
    }) || (() => {});
    return unsubscribe;
  }, [option, value]);
  useEffect(() => {
    const checkDarkMode = () => {
      if (document.documentElement.classList.contains("dark")) {
        setIsDarkMode(true);
      } else if (document.documentElement.classList.contains("light")) {
        setIsDarkMode(false);
      } else {
        setIsDarkMode(window.matchMedia("(prefers-color-scheme: dark)").matches);
      }
    };
    checkDarkMode();
    const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
    const handleMediaChange = e => {
      if (!document.documentElement.classList.contains("dark") && !document.documentElement.classList.contains("light")) {
        setIsDarkMode(e.matches);
      }
    };
    mediaQuery.addEventListener("change", handleMediaChange);
    const observer = new MutationObserver(() => {
      checkDarkMode();
    });
    observer.observe(document.documentElement, {
      attributes: true,
      attributeFilter: ["class"]
    });
    return () => {
      mediaQuery.removeEventListener("change", handleMediaChange);
      observer.disconnect();
    };
  }, []);
  const handleClick = () => {
    window.triggerChoice?.(option, value);
  };
  const handleKeyDown = e => {
    if (e.key === "Enter" || e.key === " ") {
      e.preventDefault();
      handleClick();
    }
  };
  const getColorStyles = () => {
    const baseStyles = {
      border: isDarkMode ? "1px dashed #4a5568" : "1px dashed #e1e5e9",
      cursor: "pointer",
      padding: "20px",
      position: "relative",
      backgroundColor: isDarkMode ? "#2d3748" : "#f8f9fa",
      outline: "none",
      height: "100%",
      borderRadius: "8px",
      transition: "all 0.2s ease",
      display: "flex",
      flexDirection: "column"
    };
    const colorMap = {
      green: {
        light: {
          backgroundColor: "#d4edda",
          borderColor: "#28a745"
        },
        dark: {
          backgroundColor: "#1a3a2a",
          borderColor: "#66bb6a"
        }
      },
      red: {
        light: {
          backgroundColor: "#f8d7da",
          borderColor: "#dc3545"
        },
        dark: {
          backgroundColor: "#3a1a1a",
          borderColor: "#ef5350"
        }
      },
      blue: {
        light: {
          backgroundColor: "#d1ecf1",
          borderColor: "#0c5460"
        },
        dark: {
          backgroundColor: "#1a2a3a",
          borderColor: "#42a5f5"
        }
      }
    };
    const colorStyles = colorMap[color]?.[isDarkMode ? "dark" : "light"] || ({});
    if (isSelected) {
      return {
        ...baseStyles,
        ...colorStyles,
        borderStyle: "solid",
        borderWidth: "3px",
        borderColor: colorStyles.borderColor || (isDarkMode ? "#42a5f5" : "#0061d5"),
        backgroundColor: colorStyles.backgroundColor || (isDarkMode ? "#1a2a3a" : "#e3f2fd"),
        boxShadow: isDarkMode ? "0 2px 8px rgba(66, 165, 245, 0.3)" : "0 2px 8px rgba(0, 97, 213, 0.3)",
        transform: "scale(1.02)"
      };
    }
    if (hasOptionTriggered && !isSelected) {
      return {
        ...baseStyles,
        ...colorStyles,
        opacity: 0.5
      };
    }
    return {
      ...baseStyles,
      ...colorStyles
    };
  };
  const iconStyles = {
    float: "left",
    position: "relative",
    top: "2px",
    marginRight: "12px",
    width: "20px",
    height: "20px",
    color: isSelected ? isDarkMode ? "#42a5f5" : "#0061d5" : isDarkMode ? "#a0aec0" : "#666"
  };
  return <div onClick={handleClick} style={getColorStyles()} tabIndex={0} onKeyDown={handleKeyDown} {...props}>
      <div style={iconStyles}>
        {isSelected ? <svg viewBox="0 0 24 24" fill="currentColor" style={{
    width: "100%",
    height: "100%"
  }}>
            <path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z" />
          </svg> : <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" style={{
    width: "100%",
    height: "100%"
  }}>
            <circle cx="12" cy="12" r="10" />
          </svg>}
      </div>
      <div style={{
    flex: 1
  }} className="choose-content">
        {children}
      </div>
    </div>;
};

<ProgressBar
  pages={[
                      "guides/collaborations/connect-slack-to-group-collabs/configure-slack",
                      "guides/collaborations/connect-slack-to-group-collabs/configure-box",
                      "guides/collaborations/connect-slack-to-group-collabs/scaffold-application-code",
                      "guides/collaborations/connect-slack-to-group-collabs/handle-slack-events",
                      "guides/collaborations/connect-slack-to-group-collabs/connect-box-functions",
                      "guides/collaborations/connect-slack-to-group-collabs/test-bot"
                    ]}
/>

The first step in this guide is to create and configure a Slack application.
This Slack application will act as a bot that listens for user events in Slack
channels, and responds to **slash commands** by users in those channels -
allowing them to share Box files and folders with the group.

This section will take you through the following steps.

* Create a minimal Slack application within the Slack API dashboard
* Configure the Slack application to send notifications to our application whenever a user joins or leaves the channel - allowing our code to update the Box Box group.
* Configure a `/boxadd` **slash command** that will allow users to share a Box file or folder with all the users in the channel.

## Create a minimal Slack app

Go to the **[Slack apps page][slack-apps]** and click **Create an App**. Add
an **App Name**, select your **Development Slack Workspace** from the dropdown
list where the bot will be deployed to, then click **Create App**.

<Frame noborder center shadow>
  <img src="https://mintcdn.com/box/J_EwM_J-GUl8Mc67/guides/collaborations/connect-slack-to-group-collabs/img/slack_1_create_slack_app.png?fit=max&auto=format&n=J_EwM_J-GUl8Mc67&q=85&s=e337c900672b59b9cee4f97756e8758c" alt="Create a Slack App" width="558" height="458" data-path="guides/collaborations/connect-slack-to-group-collabs/img/slack_1_create_slack_app.png" />
</Frame>

Once created, you will be redirected to the basic information section of the
application. You may adjust the icon and description of your app within the
**Display Information** section at the bottom to customize the application
in your workspace.

## Configure the Slack app's event listener

Setting up an event listener for our Slack app will allow us to monitor for
events within the channel. For this bot, we want to monitor three
[Slack events][slack-events] in order to perform actions within Box.

* [`bot_added`][slack-event-bot-added]: When the bot is first added to a channel, it will get a list of all users in the channel, then create a Box group for those users. We can then use this group later on to add that group to any content that is shared with the **slash command**.
* [`member_joined_channel`][slack-event-member-joined]: When a new user joins a Slack channel they will be added to the Box group.
* [`member_left_channel`][slack-event-member-left]: When a user leaves a Slack channel, or the user is removed, they will be removed from the Box group.

To set up a notification URL to which these Slack event payloads will
be sent, Slack requires a verification step. When you set an event listener URL
for your bot application code, Slack will immediately send a challenge to that
URL to verify that it's valid. This will be an HTTP POST with a payload that
looks something like the following:

```json theme={null}
{
  "token": "Jhj5dZrVaK7ZwHHjRyZWjbDl",
  "challenge": "3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P",
  "type": "url_verification"
}
```

To set up the URL for the event listener, that URL that is set needs
to respond with a verification payload containing the challenge value back to
Slack during this step. The payload will look similar to the following.

```js theme={null}
HTTP 200 OK Content-type: application/json {"challenge":"3eZbrw1aBm2rZgRNFdxV2595E9CY3gmdALWMmHkvFXO7tYXAYM8P"}
```

To do this we will deploy a small bit of code to respond to the challenge
event. Choose your preferred language / framework below to get started.

<Grid columns="2" compact>
  <Choose option="programming.platform" value="node" color="blue">
    # Node (Express Framework)
  </Choose>

  <Choose option="programming.platform" value="java" color="blue">
    # Java (Spring Boot Framework)
  </Choose>
</Grid>

<Choice option="programming.platform" value="node" color="none">
  Within the project directory, run `npm install express --save` to install the
  Express dependency, then deploy the following code to your public endpoint
  along with the appropriate Node modules.

  ```js theme={null}
  const express = require('express');
  const app = express();
  const port = process.env.PORT || 3000;

  app.use(express.urlencoded({ extended: true }));
  app.use(express.json());

  app.post('/event', (req, res) => {
      if (
          req.body &&
          req.body.challenge &&
          req.body.type === 'url_verification'
      ) {
          res.send({
              challenge: req.body.challenge
          });
      } else {
          res.status(400).send({
              error: "Unrecognized request"
          });
      }
  });

  app.listen(port, function(err) {
      console.log("Server listening on PORT", port);
  });
  ```
</Choice>

<Choice option="programming.platform" value="java" color="none">
  <Tip>
    [`Spring Initializr`][spring-initializr] is a useful service for
    auto-generating a new Spring boot application with all dependencies defined.
    This may be used instead of creating a blank Java application.
  </Tip>

  * From Eclipse, create a new project. When prompted, select a Gradle project.
  * Enter a unique name for the project, we used `slack.box` for this guide.
  * Open your `build.gradle` file and add the following. Ensure that the group matches the group that you used for the application. Once saved, refresh the Gradle project.

  ```java theme={null}
  plugins {
      id 'org.springframework.boot' version '2.3.1.RELEASE'
      id 'io.spring.dependency-management' version '1.0.9.RELEASE'
      id 'java'
  }

  group = 'com.box'
  version = '0.0.1-SNAPSHOT'
  sourceCompatibility = '1.8'

  repositories {
      mavenCentral()
  }

  dependencies {
      implementation 'org.springframework.boot:spring-boot-starter-web'
      testImplementation('org.springframework.boot:spring-boot-starter-test') {
          exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
      }
      compile 'com.box:box-java-sdk:2.44.1'
  }

  test {
      useJUnitPlatform()
  }
  ```

  * Within your `src/main/java` path, create a new Java class file named `Application.java`.
  * Open the file, add the following code, and save.

  ```java theme={null}
  package com.box.slack.box;

  import org.jose4j.json.internal.json_simple.JSONObject;
  import org.jose4j.json.internal.json_simple.parser.JSONParser;
  import org.springframework.boot.SpringApplication;
  import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
  import org.springframework.web.bind.annotation.PostMapping;
  import org.springframework.web.bind.annotation.RequestBody;
  import org.springframework.web.bind.annotation.RestController;

  @RestController
  @EnableAutoConfiguration
  public class Application {
      @PostMapping("/event")
      public JSONObject challenge(@RequestBody String data) throws Exception {
          JSONObject returnJSON = new JSONObject();

          Object dataObj = new JSONParser().parse(data);
          JSONObject inputJSON = (JSONObject) dataObj;
          String challenge = (String) inputJSON.get("challenge");
          String type = (String) inputJSON.get("type");

          if (type.equals("url_verification")) {
              returnJSON.put("challenge", challenge);
          } else {
              System.err.println("Invalid input");
          }

          return returnJSON;
      }

      public static void main(String[] args) {
          SpringApplication.run(Application.class, args);
      }
  }
  ```
</Choice>

<Choice option="programming.platform" unset color="none">
  <Danger>
    **Incomplete previous step**
    Please select a preferred language / framework above to get started.
  </Danger>
</Choice>

Now that we have the code to respond to the Slack challenge when adding an
event URL, we can configure that within the Slack application.

From your Slack application **Basic Information** tab, under
**Add features and functionality**, click on the button titled
**Event Subscriptions** and do the following.

* Toggle **Enable Events** to **On**.
* Under **Request URL** add in the public URL that you deployed the above code to, and be aware that we are listening at `{YOUR_APP_DOMAIN}/event` (such as `https://myapp.com/event`). Once you add the URL and click outside the field, Slack will immediately send the challenge to the URL that you were hosting the code at above. If the code responds correctly, you will see a green verified note beside the **Request URL** header.

<Frame noborder center shadow>
  <img src="https://mintcdn.com/box/J_EwM_J-GUl8Mc67/guides/collaborations/connect-slack-to-group-collabs/img/slack_1_create_event_sub.png?fit=max&auto=format&n=J_EwM_J-GUl8Mc67&q=85&s=ca88faededee0239a74b7b505807e0ab" alt="Enable Slack Event Subscriptions" width="662" height="293" data-path="guides/collaborations/connect-slack-to-group-collabs/img/slack_1_create_event_sub.png" />
</Frame>

* Expand the **Subscribe to bot events** section and click on the **Add Bot User Event** button.
* Add `member_joined_channel` and `member_left_channel` to the events the bot is subscribed to. These will send events when anyone new is added to the channel.
* Click the **Save Changes** button at the bottom of the page.

## Configure the Slack app slash command

To provide every user in a Slack channel access to a file or folder in Box, we
can use a Slack **"slash commands"**. A slash command will allow any person in
the channel to share content they own in Box with the rest of the channel.

Through this command, a channel member will be able to type
`/boxadd [file / folder] [id]`, for example `boxadd file 1459732312`, into the
channel to share the file / folder with every user in the channel. To do this,
the file is automatically collaborated with the Box group of users that are in
that channel.

From the **Basic Information** tab of your application, under **Add features and
functionality**, click on the button titled **Slash Commands**.

In the page that comes up, click **Create New Command** and input the following:

* **Command**: This is the command that a channel user will use to share a Box file / folder ID with the channel. Use `/boxadd` for this quick start.
* **Request URL**: The URL that is listening for and responding to slash commands in our Slack bot. In this quick start we use the same event URL that was used for the app event listener section above.
* **Short Description**: A description of what the Slash command will do.
* **Usage Hint**: Additional parameters that may be passed to the command. In our case, that's the Box file / folder ID and type of content.

<Frame noborder center shadow>
  <img src="https://mintcdn.com/box/J_EwM_J-GUl8Mc67/guides/collaborations/connect-slack-to-group-collabs/img/slack_1_create_slash_command.png?fit=max&auto=format&n=J_EwM_J-GUl8Mc67&q=85&s=3dc2aac91b2a5b11956a4a0acb77a08e" alt="Create Slack Slash Command" width="556" height="728" data-path="guides/collaborations/connect-slack-to-group-collabs/img/slack_1_create_slash_command.png" />
</Frame>

Click **Save** to add the command to our Slack app.

## Add Additional Scopes

When slash commands or notifications are sent to our application from
Slack they will contain a Slack user ID, which relates to the person that took
or was affected by the action. To translate that ID to a Box user we need to
get the Slack user's email, which we can then use to associate that Slack user
to a corresponding Box user. This action requires two extra scopes in the Slack
application configuration.

From your Slack application configuration, click on **OAuth & Permissions** in
the left menu, then do the following.

* Scroll down to the **Scopes** section.
* Click on the **Add an OAuth Scope** button under **Bot Token Scopes**..
* Search for and add `users:read` and `users:read.email`.

## Deploy Bot to Slack Workspace

The last step is to install the application into your Slack workspace. From the
**Basic Information** page of the app, expand the
**Install your app to your workspace** section.

<Frame noborder center shadow>
  <img src="https://mintcdn.com/box/7Uky7FPvsR-xkqwh/guides/collaborations/connect-slack-to-group-collabs/img/slack_5_install_workspace.png?fit=max&auto=format&n=7Uky7FPvsR-xkqwh&q=85&s=6e7f28c599f5fdb2551adb093389e29f" alt="Enable Slack Event Subscriptions" width="981" height="503" data-path="guides/collaborations/connect-slack-to-group-collabs/img/slack_5_install_workspace.png" />
</Frame>

Click the button to **Install App to Workspace**.

<Frame noborder center shadow>
  <img src="https://mintcdn.com/box/7Uky7FPvsR-xkqwh/guides/collaborations/connect-slack-to-group-collabs/img/slack_5_install_workspace_allow.png?fit=max&auto=format&n=7Uky7FPvsR-xkqwh&q=85&s=43233f15f6b96d91696d7f35f3ecf4d7" alt="Enable Slack Event Subscriptions" width="519" height="468" data-path="guides/collaborations/connect-slack-to-group-collabs/img/slack_5_install_workspace_allow.png" />
</Frame>

Once the **Allow** button is clicked you should see a success message. Your bot
is now installed within the workplace.

## Summary

* You've created your Slack application.
* You've configured user event notifications, slash commands, and additional scoping.
* You've deployed your Slack bot to your workspace.

<Observe option="programming.platform" value="node,java">
  <Next>I have my local application set up</Next>
</Observe>

[slack-apps]: https://api.slack.com/apps

[slack-events]: https://api.slack.com/events

[slack-event-bot-added]: https://api.slack.com/events/bot_added

[slack-event-member-joined]: https://api.slack.com/events/member_joined_channel

[slack-event-member-left]: https://api.slack.com/events/member_left_channel

[step3]: /guides/collaborations/connect-slack-to-group-collabs/scaffold-application-code

[spring-initializr]: https://start.spring.io/
