Slack as the Developer-Friendly Messaging Platform
Slack's Events API is widely regarded as one of the most well-designed real-time event delivery systems in enterprise SaaS. Its documentation is comprehensive, its SDKs are maintained, and its developer experience is significantly better than most competitors. For integration engineers, Slack is often the easiest side of a Slack ↔ Teams bridge to build.
But "easy" does not mean "simple." Slack's API architecture has specific characteristics — around event delivery, rate limiting, workspace scoping, and socket mode vs. webhook mode — that have material implications for how a reliable messaging bridge must be designed.
The Two Event Delivery Models
Webhook mode (HTTP Event Subscriptions)
In webhook mode, Slack sends events to an HTTPS endpoint registered in your Slack app configuration. When a message is posted in a subscribed channel:
- Slack sends a POST request to your endpoint within ~200ms
- Your endpoint must respond with a 200 HTTP status within 3 seconds
- If your endpoint fails to respond in 3 seconds, Slack marks the delivery as failed and retries
Reliability implication: Your event endpoint must be fast. Any processing that might take more than ~1 second (database writes, API calls to downstream systems) must be deferred to an async queue. The endpoint should acknowledge immediately, enqueue the event, and process it asynchronously.
Slack retries failed deliveries with exponential backoff. If your endpoint is down for an extended period, Slack will stop retrying after a defined number of failures and will send a app_rate_limited notification to the app owner.
Socket Mode
Socket Mode is an alternative delivery mechanism where your app maintains a persistent WebSocket connection to Slack's event infrastructure rather than receiving events at an HTTPS endpoint. Events are delivered over the WebSocket.
Socket Mode is designed for development environments where exposing a public HTTPS endpoint is impractical — it is not the recommended production architecture. The persistent WebSocket connection introduces its own reliability challenges: connection drops, reconnection backoff, and the operational complexity of maintaining a stateful TCP connection in a distributed service.
For production messaging bridges, webhook mode with a publicly accessible HTTPS endpoint is the correct architecture.
Event Types Relevant to a Messaging Bridge
The Slack Events API exposes hundreds of event types. For a messaging bridge, the relevant subset is:
| Event | When It Fires | Bridge Action |
|---|---|---|
message.channels | New message in a public channel | Route to destination platform |
message.groups | New message in a private channel | Route to destination platform (if bridge has access) |
message.im | New DM message | Route (if DM bridging is configured) |
message (with subtype: message_changed) | Message edited | Update corresponding message on destination |
message (with subtype: message_deleted) | Message deleted | Delete corresponding message on destination |
file_shared | File uploaded to channel | Transfer to destination platform |
reaction_added | Emoji reaction added | Sync reaction to destination |
The edit and delete events are where many integration implementations fail. A bridge that only handles message.channels will not propagate edits or deletions — leaving the destination platform in a permanently diverged state.
OAuth Scopes and the Workspace Constraint
Slack's permission model is workspace-scoped. An app installed into a Slack workspace can only read and write to channels within that workspace. There is no tenant-level token that spans multiple workspaces — each workspace requires a separate OAuth installation.
For enterprise organizations running Slack Enterprise Grid (a parent/child workspace structure), the Grid's Org-level token (xoxp-) provides broader access — but this is an Enterprise Grid feature only available on the most expensive Slack tier.
The required OAuth scopes for a messaging bridge:
channels:history— read message history from public channelschannels:read— list channels and resolve channel IDschat:write— post messages to channelsusers:read— resolve user identities for sender attributionfiles:read+files:write— read and write file transfers (if file bridging is required)
Request only these scopes. Avoid admin:* scopes — they are unnecessary for a messaging bridge and create a significant security surface.
Rate Limiting Architecture
Slack's rate limiting operates at the Workspace-App level (per app per workspace). The limits vary by API method tier:
| Tier | Rate Limit | Methods |
|---|---|---|
| Tier 1 | 1 req/minute | Infrequent admin methods |
| Tier 2 | 20 req/minute | Most read methods (conversations.history) |
| Tier 3 | 50 req/minute | Common methods (conversations.list) |
| Tier 4 | 100+ req/minute | High-frequency methods (chat.postMessage) |
chat.postMessage (the primary method for sending bridged messages) is Tier 4: approximately 1 request/second per channel as a practical limit.
The echo loop problem: When a bridged message arrives from Teams and is posted to Slack via chat.postMessage, Slack fires a message.channels event for the newly posted message — which would be picked up by the Events API subscription and routed back to Teams, creating an infinite loop.
Preventing echo loops requires one of two approaches:
- Bot-user filtering: Mark all bridged messages as sent by a dedicated bot user, then filter out events where the sending user is the bot
- Message ID deduplication: Maintain a short-lived cache of recently bridged message IDs and filter out events for any ID in the cache
SyncRivo uses both approaches in combination — bot-user filtering as the primary mechanism, with a 30-second deduplication cache as the safety net.
Thread Mapping and Reply Routing
Slack's threading model uses a thread_ts field — the timestamp of the parent message — to identify thread replies. Posting a reply to a thread requires including thread_ts in the chat.postMessage call:
{
"channel": "C01234567",
"text": "This is a threaded reply",
"thread_ts": "1617000000.000100"
}
For a cross-platform bridge, this means the bridge must maintain a mapping of:
- Teams message ID → Slack thread_ts (for routing Teams replies to Slack)
- Slack thread_ts → Teams message ID (for routing Slack replies to Teams)
This bidirectional mapping is the state that distinguishes a real-time interoperability bridge from a simple notification bot. It must be maintained in a persistent, low-latency store (Redis is the standard choice) with a TTL appropriate for the conversation window you want to support.
Read the Teams Graph API deep dive → | See the Slack ↔ Teams integration →