Go to Slack

Developing for Enterprise Grid

New to building on Slack? We're happy you're here. The first step is to create an app — it'll only take a moment. Get started.

Slack Enterprise Grid allows large organizations to collaborate across many workspaces. While many apps, integrations, and bots will work as expected on Enterprise, there are enough new behaviors, conditions, nuances, and opportunity to warrant reviewing your app for full compatibility.

An Enterprise organization

What is Enterprise Grid?

Enterprise Grid is a "network" of two or more Slack workspace instances. Each Slack workspace has its own team ID, its own directory of members, its own channels, conversations, files, and zeitgeist. For the most part, each workspace behaves and acts functions as you're used to.

The biggest delta for your app, integration, bot, or mind to handle is that some channels and conversations can be shared between multiple workspaces within the same Enterprise organization.

As with typical Slack workspaces, workspaces on an Enterprise Grid have their very own application installations and custom integrations.

What if I do nothing?

A Slack team is a Slack team is a Slack team. And a team is a workspace. A place where work happens. It's all very serious.

But really, if you're used to calling a team's usage of Slack itself a "team," we now refer to them as workspaces — in and outside of Enterprise Grid.

If your app or integration is never installed on an enterprise organization's workspace, you won't encounter any of the particularities detailed in this document.

However, if you have the good fortune to be installed by one or more Enterprise Grid workspaces, or a workspace you're already installed on becomes part of one:

🙃 Your app might not know what to do with messages and users originating from shared channels. It's novel.

🗿 Your app may have trouble dealing with object IDs beginning with atypical characters, like user IDs starting with W.

👯 Your bot could blindly reply multiple times to messages, not recognizing a unique message delivery scenario introduced by shared channels.

🐋 Workspaces on Enterprise Grid are often large and some API responses, like channel memberships, can grow immense. Your app might need to change how it digests information.

You never know when a workspace on Enterprise Grid will install your app. Without making a few tweaks, your new users may notice these (and other) quirks in your app's behavior.

Surface area

Working with Enterprise Grid means that no matter what your app does or which APIs it uses, it's more likely to encounter an enterprise-related quirk.

Bot users are more likely than other integration points to require additional work for exemplary performance on Enterprise Grid workspaces.

We recommend reviewing your app's current assumptions, operations, and configuration. Maybe even if you aren't planning to work with Enterprise Grid.

Read on if:

  • you are building custom integrations or Slack apps for your own Enterprise Grid workspace
  • you're building Slack apps for other Enterprise Grid workspaces to collaborate with
  • you're readying Enterprise Grid support for tools, libraries, or frameworks

Really, if you're using any aspect of the platform at all and want to support Enterprise Grid — this guide is for you.

Enterprise Grid containment grid

Here's one way of looking at the kind of work involved in supporting Enterprise Grid workspaces.

"If there's a steady paycheck in it, I'll believe anything you say."

API Support strategies
All slack apps & custom integrations
  • Support global user IDs
  • Support users from other workspaces within the same Enterprise Grid using your application features
RTM
  • Support events & messages containing global user IDs
  • Support users from other workspaces in shared channels
  • Support duplicate message deliveries in shared channels when installed on multiple workspaces
  • Connect using rtm.connect instead of rtm.start
Events API
  • Support events & messages containing global user IDs
  • Support users from other workspaces in shared channels
Web API
  • Support user ID fields containing global user IDs
  • Support users from other workspaces in shared channels
  • Use users.info to retrieve additional information on cross-workspace user IDs not found in users.list
Incoming webhooks
  • When using webhooks exclusively, no special Enterprise Grid considerations should be necessary.
Slash commands
  • Turn on entity resolution for mentioned users, allowing you to identify them by ID
  • Support user ID fields containing global user IDs
  • Slash commands are only invoked by users belonging to the workspace it is installed in
Message buttons
  • Handle action invocations by users from other workspaces
  • Support user ID fields containing global user IDs

Identifying Enterprise Grid workspaces

Workspaces don't always start out part of an Enterprise. Thanks to mergers, acquisitions, and simple evolution a workspace may become part of an Enterprise Grid.

And just as a user that's part of a workspace becomes part of its workspace's Enterprise Grid, that user who was once a duplicative singleton at Workspace A and Workspace B, with no way to reconcile shared identity, becomes a cross-referenced entity you can recognize consistently across workspaces.

There are two ways to determine whether the workspace you're working with is part of an Enterprise.

Look for enterprise IDs

The easiest way to determine whether your app is dealing with a workspace that's part of an enterprise organization is by observing enterprise_id attributes scattered through object types in Web API methods & events.

Look for enterprise_id in auth.test, the most conspicuous and eternally useful location it surfaces in.

We recommend using this method after acquiring an OAuth token. You'll want to poll it regularly for all workspaces you service, as workspaces migrate to Enterprise Grid.

An enterprise_id is constant and unique.

Other places to find enterprise_id (often paired with enterprise_name):

If you store data, you'll want to warehouse enterprise_id. Just as you'd never store information about a user or their data without tying it to a workspace first, it's best to associate Enterprise Grid workspace data with its enterprise_id.

Use enterprise_id prodigiously in your queries. 

Interpret other arcane signals

If you're working with a custom integration instead of a Slack app, you may have a more difficult time understanding whether you're working with an Enterprise Grid workspace or not.

Though you yourself should know whether the workspace you're part of and building an integration for is on Enterprise Grid, there are fewer programmatic signals to interpret and off-the-shelf code may not know how to handle certain situations like shared channels.

How do I become Enterprise-ready?

The first step is to recognize that you're working with an Enterprise Grid workspace. Then you just need to teach your app a few new tricks:

  1. Look for enterprise_id in auth.test and channels.info. Consider it a strong signal you may encounter shared channels, novel IDs, and different API responses when working with this workspace, and it might be related to another workspace that has or will install your app. You'll want to store it along side other data about its workspaces.
  2. Prepare for your integration or bot to be used in shared channels: you'll encounter messages authored by users not belonging to the workspace your app is installed in. Use users.info to look up information about user IDs belonging to a different workspace on the Enterprise Grid, ad hoc as needed.
  3. Anticipate receiving & handling semi-duplicate messages when connected on behalf of multiple workspaces within the same Enterprise Grid. If your bot is installed on 3 workspaces in an Enterprise you're invited to a shared channel with members from all 3 workspaces, you'll receive a message three times, once for each workspace. Your bot should only respond to one of the message deliveries.
  4. If your app is part of the directory, you'll want to let us know that you've prepared for Enterprise Grid workspaces. We'll do some additional testing and give you feedback on anything that could be improved.

Handling unknown users gracefully

If your app or integration is used as part of a shared channel, it will encounter users it is not aware of.

Typically, apps build their "known universe" of users by consuming the users collection in rtm.start or by using users.list. You won't find users from other workspaces in the payloads of these methods.

To find users from another workspace on an Enterprise Grid, use users.info and query by their "foreign" user ID.

Of course, many apps need only a user's ID and don't need additional information to operate.

Messages, interactions, & slash command invocations involving unknown users

Not only will you encounter user IDs belonging to other workspaces, but also the artifacts of their existence and workspace cross-pollination:

  • Messages will be authored by these users, and if they fall within your bot or app's functionality, you should respond as you normally would. Messages may also mention users on other workspaces.
  • If your messages contain interactive elements, you may receive invoked actions from these users, and should handle them just as you would a "native" workspace user
  • If you provide a slash command, you'll want to handle mentions of members from other workspace's. We strongly recommend turning on entity resolution so you can resolve mentioned users.

Careful with those messages, bot

If your application is installed by multiple workspaces of an Enterprise Grid and then used in a shared channel, it's possible that your bot will receive multiple RTM events for the same message: one for each of the workspaces you're installed on.

If your bot doesn't de-duplicate the messages by looking at the ts value of each message, you might interpret each one independently and reply to them, adding noise a conversation.

Look for the source_team message field to identify the Enterprise workspace the message originates from.

RTM considerations

To help you understand the different scenarios in which you'll receive multiple messages, let's imagine the following situation:

  • We have 3 workspaces in an organization
  • Of the 3 workspaces, your app is installed on Workspace 1 and Workspace 2
  • Your app is not installed on Workspace 3
  • Your bot has been invited to join a shared channel that exists between users from all 3 workspaces.
  • Your app has opened websocket connections for both Workspace 1 and Workspace 2
Condition Result
User from Workspace 1 sends message
  • RTM websocket for Workspace 1 will receive the message as normal
  • RTM websocket for Workspace 2 will receive the same message with some additional metadata about Workspace 1's user.
User from Workspace 2 sends message
  • RTM websocket for Workspace 2 will receive the message as normal
  • RTM websocket for Workspace 1 will receive the same message with some additional metadata about Workspace 2's user.
User from Workspace 3 sends message
  • RTM websocket for Workspace 1 will receive the message with some additional metadata.
  • RTM websocket for Workspace 2 will also receive the same message with the same additional metadata.

One way to handle duplicate messages is to make one of the workspaces in the shared channel (that your app is installed on) responsible for handling all messages coming from that shared channel.

To do this, you'll need to listen to the channel.join message when your bot is added to a shared channel. The metadata included in this event will tell you which workspaces are part of the shared channel.

Of the workspaces in the channel that your app is installed on, you'll want to pick one and save both the channel ID and team ID in your database. From that point on, you can look up the channel ID for every message you need to respond to and determine which workspace's RTM should respond. Alternatively, you can ignore all messages coming from a workspace that is not the same as the workspace your app is installed on. While simpler to implement, it will prevent users on workspaces that haven't installed your app from being able to interact with your bot.

Working with direct messages

Direct messages work much like channels: private conversations between two or more individuals spanning multiple workspaces within an Enterprise Grid result in RTM API streaming one message for each of your open websocket connections.

Your app can be the target of a direct message from another workspace across the grid. You never know when a user might want to collaborate with your bot.

These messages will also contain a source_team attribute when perspectively appropriate. The source_team attribute contains the workspace within the Enterprise Grid that the message originates from.

As with channels, when connected to multiple websocket connections on behalf of workspaces in the Enterprise Grid, you can receive redundant message deliveries. They will have the same ts value.

Sweet relief delivered by the Events API

Congratulations! If you're using the Events API, you do not have to worry about duplicate messages from shared channels. We'll only send one event regardless of how many workspaces within the organization your app is installed on.

Look for the authed_teams node that arrives as part of the "wrapper" around delivered events. It contains a collection of team IDs within an organization that this delivery is on behalf of.

Here's an example payload of a shared channel's message being delivered on behalf of multiple workspaces:

{
    "token": "ecto1EZslimer",
    "team_id": "T3MQV36V7",
    "api_app_id": "A3MERH269",
    "event": {
        "type": "message",
        "user": "W3M73E5LM",
        "text": "Now _we_ are the real ghostbusters!",
        "team": "T3MQV36V7",
        "source_team": "T3MQV36V7",
        "user_team": "T3MQV36V7",
        "user_profile": {
            "avatar_hash": "g56821b98743",
            "image_72": "https://...png",
            "first_name": "Patty",
            "real_name": "Patty Tolan",
            "name": "patty.tolan"
        },
        "ts": "1485371714.000002",
        "channel": "C3W4KDKCL",
        "event_ts": "1485371714.000002"
    },
    "type": "event_callback",
    "authed_users": [
        "W3MBP5799"
    ],
    "authed_teams": [
        "T3NHPD4J0",
        "T3MQV36V7"
    ]
}

In this example, the application is installed on two workspaces in an Enterprise and is party to a conversation in a shared channel.

The message originates from team T3MQV36V7 and is delivered as a single event for both teams, including T3NHPD4J0.

Shared channels

Workspaces on an Enterprise Grid may contain channels shared between multiple workspaces in an organization. This means that it may contain members from workspaces your app is not installed on and that you don't have direct access to data around.

Determine whether you're working with a shared channel by looking for the is_org_shared attribute and whether it is set to true. You'll find this field on channel objects returned by channels.list and channels.info.

If you're only installed on one of the workspaces involved, all channel members belonging to the other workspace are "foreign" to your app.

Use users.info to look up user information about a remote workspace's shared user.

Crossing the streams

If your app is installed on multiple workspaces within an enterprise organization, you may want to utilize that position effectively by grouping data by enterprise_id and using the new enterprise user IDs as your primary source of truth.

This way you'll easily cross work streams between more than one workspace without exploding at the speed of light.

A translation layer for the Enterprise Grid

When a pre-existing workspace becomes part of an Enterprise Grid, each existing user is assigned a new "global" user ID representing that user across all workspaces in the grid.

Slack provides a translation layer that hides this double accounting from your app, providing you with the user's "original" local user ID instead whenever possible; the translation layer is not pervasive.

You can turn this translation layer off and on. By turning it off, the API will serve your app only global user IDs.

We recommend disabling the translation layer and using migration.exchange to convert any stored local user IDs into global user IDs, your app can transact exclusively with global user IDs for all Enterprise Grid organizations.

How global and local IDs differ

In typical workspaces, users have a single user ID local to that workspace.

When workspaces bind together with others to form an Enterprise Grid, the existing users are granted a global user ID to complement their local user ID.

From then on, every new user is granted only a global user ID.

All users in an enterprise grid have a global user ID, a string beginning with W. Global user IDs are a unique, consistent identifier for a single user across multiple workspaces in an Enterprise Grid. It travels with users.

Local user IDs are unique only to a single workspace and begin with U. With translation turned off, your app will no longer receive local user IDs. Users created after an enterprise migration do not have local user IDs.

Toggling the translation layer

Disable the translation layer by navigating to application management for your app and finding the User ID Translation panel.

The translation layer's toggle

If you need to update local user IDs you've stored in a database or in other ways, first use migration.exchange with the relevant user or bot tokens to receive global user IDs in bulk.

When workspaces migrate to Enterprise Grid

In most cases, an Enterprise Grid is formed by combining multiple independent Slack workspaces together. During this period of time where data is migrated and made compatible with Grid's organization structures, Web API calls, RTM API connections, Events API, and other platform interactions may be unavailable, both for users and your applications.

On the Web API, you may encounter the team_added_to_org error during this time.

Migration time varies depending team-to-team and organization-to-organization. Practice an exponential backoff strategy when encountering errors to help manage these periods by attempting connections at incrementally increasing rates: 1 second to 3 seconds to 10 seconds to 30 seconds to 1 minute, and so on.

To best plan for migrations, subscribe to grid-related app events as part of the Events API. Using these events, your app can pause and resume activity as appropriate.

If your app has disabled the translation layer, use migration.exchange to receive global user IDs for any of your stored local user IDs.

User object changes

You'll find user objects on Enterprise Grid workspaces now have an enterprise_user attribute, containing a hash of additional context about the user in the larger Enterprise Grid organization.

You'll find:

  • id - this user's ID, which might start with U or W
  • enterprise_id - the unique ID for this particular Enterprise Grid organization
  • enterprise_name - the name of this umbrella organization
  • is_admin - a boolean value indicating whether this user administers this Enterprise
  • is_owner - a boolean value indicating whether this user is the owner of this Enterprise
  • teams - an array of team (workspace) IDs within the containing enterprise organization that this user belongs to

User IDs

Within an Enterprise Grid, all users have an underlying cross-workspace ID beginning with the letter W. If a workspace existed before the workspace became part of an Enterprise Grid, users created before that transition also have a more local team-specific ID beginning with U.

We'll automatically convert references to a user's Enterprise ID to their legacy team-specific ID. This allows your app to remain backwards compatible for users you've identified on a workspace before the days of Enterprise Grid.

You'll still encounter users with IDs beginning with W: these users were created after a workspace became part of an Enterprise and have no legacy ID.

Building internal integrations

It's easy and secure to build Slack apps for your workspaces only. As with all Slack apps, installation and ownership of apps is on a workspace-basis. Internal integrations that are locked to a single workspace cannot be installed across an Enterprise Grid.

Find out more in our internal integrations documentation.

Developing for, but not necessarily against, Enterprise

The easiest way to test your app or integration against an Enterprise Grid environment is for an Enterprise Grid workspace to install it.

Of course, not every app will be installed on an Enterprise, though we hope most strive to be.

Implement the additional logic outlined in this document to prepare your app for Enterprise Grid customers. When you've finished, indicate your readiness as part of the the Slack App Directory review process.

Do you need a test instance?

After combing through your code and database structures, you've made some tweaks here and there, and you want to test if everything actually works against Enterprise Grid. Fair enough, that's reasonable.

We hope following the recommendations of this documentation makes you confident your app will work in Enterprise environments.

We have a limited number of test instances available for complex apps and bots targeting Enterprise Grid workspaces that are concerned about the impact on a large number of users. If that sounds like you, please fill out this form to signal your interest to us.

It shouldn't be necessary to use a test instance if your app makes minimal use of user data or utilizes only simple incoming webhooks.

Glossary

Though for the most part Enterprise Grid Slack workspaces are business as usual, there are enough new concepts introduced to warrant brief review:

  • Enterprise organization - An entity introduced with Enterprise Grid to house multiple Slack workspaces. When a customer is using Enterprise Grid, all users and their direct messages are stored at the organization level.
  • Enterprise organization user - A Enterprise Grid user. They have the same identity and profile across all workspaces within an organization.
  • Enterprise user ID - A user ID beginning with W that represents a user across one or more workspaces within an Enterprise.
  • Legacy user ID - Also known as a "team user ID", these are the User IDs you've come to know and love in Slack and are unique only to a workspace. They begin with U.
  • Shared channel - A channel shared between two or more workspaces within an organization.
  • Translation layer - A translating service that transparently converts new organization-based user IDs to legacy team user IDs, allowing apps to migrate data.
  • Workspace - Where a team works. The terms, workspace and team are often used interchangeably. When you see the object name such as team_id, it means the ID for a workspace.

Related reading:

  • Announcement about user IDs - Back in August 2016, we announced that user IDs may sometimes begin with the letter W and that it vaguely had something to do with our upcoming enterprise product. W is here to stay.