The Graph API as the Integration Gateway
Microsoft Teams does not expose a native webhook system for receiving messages in real time. All integration with Teams — reading messages, posting to channels, managing subscriptions — goes through the Microsoft Graph API, which is the unified REST API for the entire Microsoft 365 ecosystem.
For integration engineers building messaging interoperability against Teams, the Graph API is both powerful and opinionated. Understanding its architecture — especially its subscription model, throttling behavior, and permission scopes — is essential for building a reliable real-time bridge.
Authentication: Azure Active Directory and OAuth 2.0
All Microsoft Graph API calls must be authenticated with an access token issued by Azure Active Directory (Azure AD). For server-to-server integration (no user interaction), the correct OAuth flow is the Client Credentials flow:
- Register an application in Azure AD (App Registrations in the Azure portal)
- Grant the application the required API permissions (Application permissions, not Delegated)
- Issue a client secret or certificate
- Exchange credentials for an access token at
https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
The access token is a JWT with a 1-hour expiry. Your integration layer must handle token refresh without interrupting message routing.
Permission scope minimization
The Microsoft Graph API has a rich permission model. For a messaging bridge, the required Application permissions are:
ChannelMessage.Read.All— read messages from all channels in the tenant (required for subscriptions)ChannelMessage.Send— post messages to channelsChannel.ReadBasic.All— enumerate channels (required to resolve channel IDs)User.Read.All— resolve user identities for sender attribution
A common integration mistake is requesting Chat.ReadWrite.All when only channel messaging is required. Broader scopes expand the blast radius if the service account is compromised. Request only what your integration needs.
The Subscription Model: How Real-Time Message Delivery Works
Teams does not push messages to integration endpoints via a traditional webhook. Instead, it uses the Graph API Change Notifications system, which operates as a subscription-based model:
- Your integration creates a subscription against a resource (e.g., a specific channel's messages):
POST /subscriptions - Microsoft Graph sends notifications to your notification URL (HTTPS endpoint) when the subscribed resource changes
- Your endpoint acknowledges the notification with a 202 response
- Your integration fetches the full message data from the Graph API using the resource URL in the notification
Subscription lifecycle management
Subscriptions expire. The maximum expiration for channel message subscriptions is 60 minutes. Your integration must continuously renew subscriptions before they expire:
PATCH /subscriptions/{subscriptionId}
{
"expirationDateTime": "2026-04-06T15:00:00Z"
}
A subscription renewal failure results in missed messages — a silent data loss, not a visible error. SyncRivo's subscription manager runs a renewal loop at 45-minute intervals, with a fallback reconciliation job that detects and recreates any expired subscriptions.
Notification URL validation
When creating a subscription, Microsoft Graph sends a validation token to your notification URL. Your endpoint must echo the token back within 10 seconds or the subscription creation fails. This is a liveness check that Microsoft runs before and during subscription renewals.
Rate Limiting and Throttling
The Microsoft Graph API implements throttling at multiple levels. For channel message operations:
- Service-level throttling: Microsoft does not publish exact rate limits, but the practical limit for
ChannelMessage.Sendis approximately 3–4 requests per second per channel before throttling begins - Retry-After header: When throttled, the API returns a 429 status with a
Retry-Afterheader specifying the backoff period in seconds - Adaptive backoff required: Any robust integration must implement exponential backoff with jitter on 429 responses
For a high-volume bridge (bridging active channels with 10+ messages per minute), the rate limiting architecture is critical. SyncRivo implements a per-channel token bucket limiter with a queue for messages that exceed the rate limit, ensuring no messages are lost during throttling events.
Adaptive Cards: The Teams Message Format
Teams channels support two message formats: plain text and Adaptive Cards. For integration engineers, understanding Adaptive Cards is essential because:
- Rich messages (with formatting, images, action buttons) must use Adaptive Card JSON
- Messages with @mentions require specific Adaptive Card formatting to resolve correctly
- The Adaptive Card schema differs from Slack Block Kit — cross-platform formatting translation requires explicit mapping
A minimal Adaptive Card for a bridged message from Slack:
{
"type": "message",
"attachments": [{
"contentType": "application/vnd.microsoft.card.adaptive",
"content": {
"type": "AdaptiveCard",
"version": "1.4",
"body": [
{
"type": "ColumnSet",
"columns": [
{
"type": "Column",
"width": "auto",
"items": [{ "type": "TextBlock", "text": "**Jordan** via Slack", "wrap": true }]
}
]
},
{ "type": "TextBlock", "text": "Message content here", "wrap": true }
]
}
}]
}
Thread replies
To post a message as a reply in an existing Teams thread, use the /replies endpoint:
POST /teams/{teamId}/channels/{channelId}/messages/{messageId}/replies
Maintaining thread coherence across platforms requires a persistent mapping store: Teams message ID ↔ Slack message ID ↔ timestamp. This is the state that makes a bridge fundamentally different from a simple notification bot.
Identity Resolution
Teams uses an aadObjectId (Azure AD object ID) to identify users. When a message arrives from Slack (where users are identified by their Slack userId), the bridge must resolve the Slack user's email to an Azure AD object ID to populate the sender attribution in the Adaptive Card.
This resolution requires a cross-platform identity map maintained in the bridge's state store. For new users, it requires a Graph API call to /users?$filter=mail eq '{email}'.
The Webhook Gap: Why Teams Cannot Be a Native Bridge Endpoint
A frequently cited limitation of Teams for integration purposes is the absence of a true outbound webhook for channel messages at the tenant level. Microsoft Graph subscriptions (described above) are the closest equivalent, but they are subscription-based, expire frequently, and require a publicly accessible HTTPS endpoint.
This architectural characteristic means a Teams messaging bridge has a higher operational complexity burden than equivalent bridges for platforms with native persistent webhook connections (Zoom Webhook V2, Slack Events API socket mode). Subscription lifecycle management, renewal failure handling, and notification URL uptime are all additional reliability concerns that do not exist for simpler webhook-based integrations.
See how SyncRivo solves Teams integration → | Read the Slack Events API deep dive →