Skip to content

Connecting OpenClaw to Microsoft Teams

You need to know that Microsoft Teams is now a plugin. It isn’t bundled with the core install anymore. This change happened on 2026.1.15. If you want to use Teams, you have to install the plugin yourself. This keeps the core install light and lets the Microsoft Teams dependencies update on their own schedule.

Install it via the CLI from the npm registry:

Terminal window
openclaw plugins install @openclaw/msteams

If you are running from a git repo, use a local checkout:

Terminal window
openclaw plugins install ./path/to/local/msteams-plugin

When you choose Teams during setup and a git checkout is detected, OpenClaw suggests the local install path for you automatically. You can find more details in the Plugins documentation.

Getting started is straightforward. You just need to follow these steps:

  1. Install the Microsoft Teams plugin.
  2. Create an Azure Bot to get your App ID and client secret.
  3. Obtain your tenant ID.
  4. Add those credentials to your OpenClaw config.
  5. Make sure /api/messages (usually port 3978) is reachable via a public URL or tunnel.
  6. Install the Teams app package and start the gateway.

Here is the minimal config you need:

{
channels: {
msteams: {
enabled: true,
appId: "<APP_ID>",
appPassword: "<APP_PASSWORD>",
tenantId: "<TENANT_ID>",
webhook: { port: 3978, path: "/api/messages" },
},
},
}

Keep in mind that group chats are blocked by default because channels.msteams.groupPolicy is set to "allowlist". If you want to allow replies in groups, you should set channels.msteams.groupAllowFrom. You can also set groupPolicy: "open" to let any member interact, though mentions are still required.

The main idea here is to let you talk to OpenClaw through Teams DMs, group chats, channels, and mentions. We want to make sure routing stays deterministic. This means replies always go back to the exact channel where the message started. We also stick to safe channel behavior, so mentions are required unless you change the settings.

By default, Microsoft Teams can handle config updates if you trigger them with the /config set|unset command. This works as long as commands.config is set to true.

If you want to turn this off, use this config:

{
channels: { msteams: { configWrites: false } },
}

Managing who can talk to your bot is a big deal for security. For direct messages, the default setting is channels.msteams.dmPolicy = "pairing". This means the bot ignores unknown senders until you approve them. When you set up channels.msteams.allowFrom, you should use stable AAD object IDs. Since UPNs and display names can change, direct name matching is turned off by default. If you need to turn it on, you have to use channels.msteams.dangerouslyAllowNameMatching: true. The setup wizard can help you resolve these names to IDs using Microsoft Graph if your credentials allow it.

For group chats and channels, the default is channels.msteams.groupPolicy = "allowlist". This blocks everyone unless you specifically add them to groupAllowFrom. If you want to change this behavior globally, use channels.defaults.groupPolicy. You can set the policy to "open" to let any member interact with the bot (though they still need to mention it), or set it to "disabled" if you want to block channel access entirely.

Example:

{
channels: {
msteams: {
groupPolicy: "allowlist",
groupAllowFrom: ["user@org.com"],
},
},
}

You can also restrict replies to specific teams and channels by listing them under channels.msteams.teams. You should use stable team IDs and channel conversation IDs for these keys. When you use an allowlist policy alongside a teams list, only those specific locations are accepted. The configuration wizard makes this easier by letting you enter Team/Channel names and storing the IDs for you. When OpenClaw starts, it tries to resolve these names to IDs and logs the mapping. If it can’t resolve a name, it keeps what you typed but won’t use it for routing unless you enable channels.msteams.dangerouslyAllowNameMatching: true.

Example:

{
channels: {
msteams: {
groupPolicy: "allowlist",
teams: {
"My Team": {
channels: {
General: { requireMention: true },
},
},
},
},
},
}

Getting the integration running follows a clear path:

  1. Install the Microsoft Teams plugin.
  2. Create an Azure Bot to get your App ID, secret, and tenant ID.
  3. Build a Teams app package that points to your bot and includes the necessary RSC permissions.
  4. Upload and install that Teams app into a team or your personal scope.
  5. Configure the msteams settings in your ~/.openclaw/openclaw.json file and start the gateway.
  6. The gateway starts listening for Bot Framework traffic on the /api/messages endpoint.

You need to set up an Azure Bot resource before you can finish the OpenClaw configuration.

  1. Head over to Create Azure Bot

  2. Fill out the Basics tab with these settings:

    FieldValue
    Bot handleYour bot name, e.g., openclaw-msteams (must be unique)
    SubscriptionSelect your Azure subscription
    Resource groupCreate new or use existing
    Pricing tierFree for dev/testing
    Type of AppSingle Tenant (recommended - see note below)
    Creation typeCreate new Microsoft App ID

Deprecation notice: You cannot create new multi-tenant bots after 2025-07-31. Use Single Tenant for all new bots.

  1. Click Review + create, then hit Create and wait a minute or two.
  1. Open your Azure Bot resource and go to Configuration.
  2. Copy the Microsoft App ID; this is your appId.
  3. Click Manage Password to go to the App Registration.
  4. Under Certificates & secrets, create a New client secret and copy the Value; this is your appPassword.
  5. Go to the Overview page and copy the Directory (tenant) ID; this is your tenantId.
  1. Go back to the Configuration section of your Azure Bot.
  2. Set the Messaging endpoint to your webhook URL:
    • For production: https://your-domain.com/api/messages
    • For local dev: Use a tunnel URL.
  1. In the Azure Bot menu, go to Channels.
  2. Select Microsoft Teams, click Configure, and hit Save.
  3. Accept the Terms of Service.

Microsoft Teams needs a public URL to send messages to, so it can’t talk to localhost directly. You can use a tunnel to bridge that gap during development.

Option A: ngrok

Terminal window
ngrok http 3978
# Copy the https URL, e.g., https://abc123.ngrok.io
# Set messaging endpoint to: https://abc123.ngrok.io/api/messages

Option B: Tailscale Funnel

Terminal window
tailscale funnel 3978
# Use your Tailscale funnel URL as the messaging endpoint

If you want to avoid hand-editing JSON manifests, you can use the Teams Developer Portal instead. It is often a much smoother path:

  1. Click + New app.
  2. Fill in the basic info like the name, description, and developer details.
  3. Go to App features → Bot.
  4. Select Enter a bot ID manually and paste your Azure Bot App ID.
  5. Check these scopes: Personal, Team, and Group Chat.
  6. Click Distribute → Download app package.
  7. In Teams: Apps → Manage your apps → Upload a custom app → select the ZIP.

Option A: Azure Web Chat (verify webhook first)

  1. In the Azure Portal, go to your Azure Bot resource and select Test in Web Chat.
  2. Send a message to see if you get a response.
  3. This confirms your webhook endpoint works before you deal with Teams-specific setup.

Option B: Teams (after app installation)

  1. Install the Teams app using sideloading or your org catalog.
  2. Find the bot in Teams and send a DM.
  3. Check your gateway logs for incoming activity to confirm the connection.
  1. Install the Microsoft Teams plugin

    • From npm: openclaw plugins install @openclaw/msteams
    • From a local checkout: openclaw plugins install ./path/to/local/msteams-plugin
  2. Bot registration

    • Create an Azure Bot and make a note of these:
      • App ID
      • Client secret (App password)
      • Tenant ID (single-tenant)
  3. Teams app manifest

    • Include a bot entry with botId = <App ID>.
    • Scopes: personal, team, groupChat.
    • supportsFiles: true is required for personal scope file handling.
    • Add RSC permissions.
    • Create icons: outline.png (32x32) and color.png (192x192).
    • Zip these three files together: manifest.json, outline.png, and color.png.
  4. Configure OpenClaw

    {
    channels: {
    msteams: {
    enabled: true,
    appId: "<APP_ID>",
    appPassword: "<APP_PASSWORD>",
    tenantId: "<TENANT_ID>",
    webhook: { port: 3978, path: "/api/messages" },
    },
    },
    }

    You can also use environment variables instead of config keys:

    • MSTEAMS_APP_ID
    • MSTEAMS_APP_PASSWORD
    • MSTEAMS_TENANT_ID
  5. Bot endpoint

    • Set the Azure Bot Messaging Endpoint to:
      • https://<host>:3978/api/messages (or your chosen path/port).
  6. Run the gateway

    • The Teams channel starts automatically when the plugin is installed and the msteams config exists with valid credentials.

OpenClaw includes a Graph-backed member-info action for Microsoft Teams. This allows agents and automations to resolve channel member details like display names, emails, and roles directly from Microsoft Graph.

Requirements:

  • Member.Read.Group RSC permission (this is already in the recommended manifest).
  • For cross-team lookups: User.Read.All Graph Application permission with admin consent.

The action is gated by channels.msteams.actions.memberInfo and is enabled by default when Graph credentials are available.

You can control how many recent channel or group messages are included in your prompt using channels.msteams.historyLimit. If you don’t set this, the system uses messages.groupChat.historyLimit as a fallback. You can set this value to 0 if you want to disable it, though it defaults to 50. When the system fetches thread history, it filters everything through sender allowlists (allowFrom / groupAllowFrom). This ensures that your thread context seeding only includes messages from senders you have actually allowed. For DM history, you can set limits using channels.msteams.dmHistoryLimit for user turns, or use channels.msteams.dms["<user_id>"].historyLimit if you need to override settings for a specific user.

These are the resource-specific permissions currently in the Teams app manifest. Keep in mind that these only apply within the specific team or chat where you have installed the app.

For channels (team scope):

  • ChannelMessage.Read.Group (Application) - receive all channel messages without an @mention
  • ChannelMessage.Send.Group (Application)
  • Member.Read.Group (Application)
  • Owner.Read.Group (Application)
  • ChannelSettings.Read.Group (Application)
  • TeamMember.Read.Group (Application)
  • TeamSettings.Read.Group (Application)

For group chats:

  • ChatMessage.Read.Chat (Application) - receive all group chat messages without an @mention

Here is a minimal, valid example showing the required fields. You will need to replace the IDs and URLs with your own values.

{
$schema: "https://developer.microsoft.com/en-us/json-schemas/teams/v1.23/MicrosoftTeams.schema.json",
manifestVersion: "1.23",
version: "1.0.0",
id: "00000000-0000-0000-0000-000000000000",
name: { short: "OpenClaw" },
developer: {
name: "Your Org",
websiteUrl: "https://example.com",
privacyUrl: "https://example.com/privacy",
termsOfUseUrl: "https://example.com/terms",
},
description: { short: "OpenClaw in Teams", full: "OpenClaw in Teams" },
icons: { outline: "outline.png", color: "color.png" },
accentColor: "#5B6DEF",
bots: [
{
botId: "11111111-1111-1111-1111-111111111111",
scopes: ["personal", "team", "groupChat"],
isNotificationOnly: false,
supportsCalling: false,
supportsVideo: false,
supportsFiles: true,
},
],
webApplicationInfo: {
id: "11111111-1111-1111-1111-111111111111",
},
authorization: {
permissions: {
resourceSpecific: [
{ name: "ChannelMessage.Read.Group", type: "Application" },
{ name: "ChannelMessage.Send.Group", type: "Application" },
{ name: "Member.Read.Group", type: "Application" },
{ name: "Owner.Read.Group", type: "Application" },
{ name: "ChannelSettings.Read.Group", type: "Application" },
{ name: "TeamMember.Read.Group", type: "Application" },
{ name: "TeamSettings.Read.Group", type: "Application" },
{ name: "ChatMessage.Read.Chat", type: "Application" },
],
},
},
}
  • The bots[].botId and webApplicationInfo.id must both match your Azure Bot App ID.
  • Ensure bots[].scopes includes every surface you plan to use, such as personal, team, or groupChat.
  • You must set bots[].supportsFiles: true to handle files in the personal scope.
  • Your authorization.permissions.resourceSpecific must include channel read and send permissions if you want to process channel traffic.

If you need to update an app that is already installed—for example, to add RSC permissions—follow these steps:

  1. Update your manifest.json with your new settings.
  2. Increment the version field, such as moving from 1.0.0 to 1.1.0.
  3. Re-zip the manifest along with your icons (manifest.json, outline.png, color.png).
  4. Upload the new zip file through the Teams Admin Center or by sideloading a custom app in Teams.
  5. For team channels, you must reinstall the app in each team for the new permissions to take effect.
  6. Fully quit and relaunch the Teams client to clear out any cached app metadata.

With Teams RSC only (app installed, no Graph API permissions)

Section titled “With Teams RSC only (app installed, no Graph API permissions)”

Works:

  • Reading and sending channel message text content.
  • Receiving personal (DM) file attachments.

Does NOT work:

  • Accessing channel or group image and file contents, as the payload only includes an HTML stub.
  • Downloading attachments from SharePoint or OneDrive and reading history beyond the live webhook event.

With Teams RSC + Microsoft Graph Application permissions

Section titled “With Teams RSC + Microsoft Graph Application permissions”

Adds:

  • Downloading hosted contents like images pasted into messages and file attachments in SharePoint or OneDrive.
  • Reading channel or chat message history through the Graph API.
CapabilityRSC PermissionsGraph API
Real-time messagesYes (via webhook)No (polling only)
Historical messagesNoYes (can query history)
Setup complexityApp manifest onlyRequires admin consent + token flow
Works offlineNo (must be running)Yes (query anytime)

RSC is designed for real-time listening, while the Graph API handles historical access. If you need to catch up on messages missed while the app was offline, you will need the Graph API with ChannelMessage.Read.All, which requires admin consent.

Graph-enabled media + history (required for channels)

Section titled “Graph-enabled media + history (required for channels)”

If you want to handle images or files in channels, or if you need to fetch message history, you’ll have to enable Microsoft Graph permissions and grant admin consent.

  1. Go to Entra ID (Azure AD) App Registration and add these Microsoft Graph Application permissions:
    • ChannelMessage.Read.All (for channel attachments and history)
    • Chat.Read.All or ChatMessage.Read.All (for group chats)
  2. Make sure to grant admin consent for the tenant.
  3. Update your Teams app manifest version, re-upload it, and reinstall the app in Teams.
  4. Fully quit and relaunch Teams to clear out any cached app metadata.

A quick note on user mentions: @mentions work fine for anyone already in the conversation. But if you want to search for and mention users who are not in the current conversation, you’ll need to add the User.Read.All (Application) permission and grant admin consent.

Teams sends messages through an HTTP webhook. If your processing takes a while—like when waiting for slow LLM responses—you might run into a few issues:

  • Gateway timeouts and Teams retrying the message (which causes duplicates)
  • Dropped replies

OpenClaw manages this by responding quickly and sending replies proactively, but keep in mind that very slow responses can still cause trouble.

Teams markdown is a bit more restricted than what you’d find in Slack or Discord:

  • Basic formatting like bold, italic, code, and links work fine.
  • Complex markdown (like tables or nested lists) might not render correctly.
  • Adaptive Cards are supported for polls and sending custom cards (see below).

Here are the key settings you’ll need (check out /gateway/configuration for shared channel patterns):

  • channels.msteams.enabled: enable/disable the channel.
  • channels.msteams.appId, channels.msteams.appPassword, channels.msteams.tenantId: bot credentials.
  • channels.msteams.webhook.port (default 3978)
  • channels.msteams.webhook.path (default /api/messages)
  • channels.msteams.dmPolicy: pairing | allowlist | open | disabled (default: pairing)
  • channels.msteams.allowFrom: DM allowlist (AAD object IDs recommended). The wizard resolves names to IDs during setup when Graph access is available.
  • channels.msteams.dangerouslyAllowNameMatching: break-glass toggle to re-enable mutable UPN/display-name matching and direct team/channel name routing.
  • channels.msteams.textChunkLimit: outbound text chunk size.
  • channels.msteams.chunkMode: length (default) or newline to split on blank lines (paragraph boundaries) before length chunking.
  • channels.msteams.mediaAllowHosts: allowlist for inbound attachment hosts (defaults to Microsoft/Teams domains).
  • channels.msteams.mediaAuthAllowHosts: allowlist for attaching Authorization headers on media retries (defaults to Graph + Bot Framework hosts).
  • channels.msteams.requireMention: require @mention in channels/groups (default true).
  • channels.msteams.replyStyle: thread | top-level (see Reply Style).
  • channels.msteams.teams.<teamId>.replyStyle: per-team override.
  • channels.msteams.teams.<teamId>.requireMention: per-team override.
  • channels.msteams.teams.<teamId>.tools: default per-team tool policy overrides (allow/deny/alsoAllow) used when a channel override is missing.
  • channels.msteams.teams.<teamId>.toolsBySender: default per-team per-sender tool policy overrides ("*" wildcard supported).
  • channels.msteams.teams.<teamId>.channels.<conversationId>.replyStyle: per-channel override.
  • channels.msteams.teams.<teamId>.channels.<conversationId>.requireMention: per-channel override.
  • channels.msteams.teams.<teamId>.channels.<conversationId>.tools: per-channel tool policy overrides (allow/deny/alsoAllow).
  • channels.msteams.teams.<teamId>.channels.<conversationId>.toolsBySender: per-channel per-sender tool policy overrides ("*" wildcard supported).
  • toolsBySender keys should use explicit prefixes: id:, e164:, username:, name: (legacy unprefixed keys still map to id: only).
  • channels.msteams.actions.memberInfo: enable or disable the Graph-backed member info action (default: enabled when Graph credentials are available).
  • channels.msteams.sharePointSiteId: SharePoint site ID for file uploads in group chats/channels (see Sending files in group chats).

Session keys use the standard agent format (you can find more on this at /concepts/session):

  • Direct messages share the main session (agent:<agentId>:<mainKey>).
  • Channel or group messages use the conversation ID:
    • agent:<agentId>:msteams:channel:<conversationId>
    • agent:<agentId>:msteams:group:<conversationId>

Teams recently updated its channel UI, giving you two different styles built on the same data model. Depending on how your channel is set up, you’ll want to choose the right replyStyle to make sure your bot’s messages look right.

StyleDescriptionRecommended replyStyle
Posts (classic)Messages appear as cards with threaded replies underneaththread (default)
Threads (Slack-like)Messages flow linearly, more like Slacktop-level

The tricky part is that the Teams API doesn’t tell you which UI style a channel is using. If you pick the wrong one, things get messy:

  • Using thread in a Threads-style channel makes replies look nested and awkward.
  • Using top-level in a Posts-style channel makes replies show up as entirely new posts instead of staying inside the thread.

To fix this, you should configure the replyStyle for each channel manually based on its setup:

{
channels: {
msteams: {
replyStyle: "thread",
teams: {
"19:abc...@thread.tacv2": {
channels: {
"19:xyz...@thread.tacv2": {
replyStyle: "top-level",
},
},
},
},
},
},
}

There are a few things to keep in mind when you deal with files and images in Teams:

  • DMs: You can send images and file attachments using the standard Teams bot file APIs.
  • Channels/groups: Files here live in M365 storage like SharePoint or OneDrive. When a webhook hits, the payload only contains an HTML stub. It doesn’t include the actual file bytes. Because of this, Graph API permissions are required if you want to download channel attachments.
  • For sending files directly, use action=upload-file with media, filePath, or path. You can use the optional message field for text and filename to change the name of the uploaded file.

If you don’t have Graph permissions, any channel messages with images will show up as text-only because the bot can’t reach the image content.

By default, OpenClaw only downloads media from Microsoft or Teams hostnames. You can change this by setting channels.msteams.mediaAllowHosts (use ["*"] to allow everything). Authorization headers are only sent to hosts listed in channels.msteams.mediaAuthAllowHosts, which defaults to Graph and Bot Framework hosts. You should keep this list short and specific.

Bots can send files in DMs using the built-in FileConsentCard flow. But if you want to send files in group chats or channels, you need a bit more setup:

ContextHow files are sentSetup needed
DMsFileConsentCard → user accepts → bot uploadsWorks out of the box
Group chats/channelsUpload to SharePoint → share linkRequires sharePointSiteId + Graph permissions
Images (any context)Base64-encoded inlineWorks out of the box

Bots don’t have their own personal OneDrive (the /me/drive Graph API endpoint doesn’t work for application identities). To send files in group chats or channels, the bot has to upload the file to a SharePoint site first and then create a link to share it.

  1. Add Graph API permissions in your Entra ID (Azure AD) App Registration:

    • Sites.ReadWrite.All (Application) - to upload files to SharePoint.
    • Chat.Read.All (Application) - optional, but lets you create per-user sharing links.
  2. Grant admin consent for your tenant.

  3. Get your SharePoint site ID:

    Terminal window
    # Via Graph Explorer or curl with a valid token:
    curl -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/sites/{hostname}:/{site-path}"
    # Example: for a site at "contoso.sharepoint.com/sites/BotFiles"
    curl -H "Authorization: Bearer $TOKEN" \
    "https://graph.microsoft.com/v1.0/sites/contoso.sharepoint.com:/sites/BotFiles"
    # Response includes: "id": "contoso.sharepoint.com,guid1,guid2"
  4. Configure OpenClaw:

    {
    channels: {
    msteams: {
    // ... other config ...
    sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",
    },
    },
    }
PermissionSharing behavior
Sites.ReadWrite.All onlyOrganization-wide sharing link (anyone in org can access)
Sites.ReadWrite.All + Chat.Read.AllPer-user sharing link (only chat members can access)

Using per-user sharing is better for security since only the people in the chat can see the file. If you don’t have the Chat.Read.All permission, the bot will just use organization-wide sharing.

ScenarioResult
Group chat + file + sharePointSiteId configuredUpload to SharePoint, send sharing link
Group chat + file + no sharePointSiteIdAttempt OneDrive upload (may fail), send text only
Personal chat + fileFileConsentCard flow (works without SharePoint)
Any context + imageBase64-encoded inline (works without SharePoint)

Any files you upload are stored in a /OpenClawShared/ folder inside the default document library of your configured SharePoint site.

OpenClaw sends Teams polls as Adaptive Cards because there isn’t a native Teams poll API for bots to use.

  • CLI: openclaw message poll --channel msteams --target conversation:<id> ...
  • The gateway records votes in ~/.openclaw/msteams-polls.json.
  • You need to keep the gateway online to record these votes.
  • Polls don’t automatically post a summary of the results yet, so you’ll need to check the store file if you need the data.

AI Setup Assistant

You can send any Adaptive Card JSON to Teams users or conversations using the message tool or the CLI.

The card parameter takes an Adaptive Card JSON object. When you provide a card, the message text is optional.

Agent tool:

{
action: "send",
channel: "msteams",
target: "user:<id>",
card: {
type: "AdaptiveCard",
version: "1.5",
body: [{ type: "TextBlock", text: "Hello!" }],
},
}

CLI:

Terminal window
openclaw message send --channel msteams \
--target "conversation:19:abc...@thread.tacv2" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello!"}]}'

Check the Adaptive Cards documentation for card schema and examples. For details on target formatting, see Target formats below.

MSTeams targets use prefixes to help you distinguish between users and conversations:

Target typeFormatExample
User (by ID)user:<aad-object-id>user:40a1a0ed-4ff2-4164-a219-55518990c197
User (by name)user:<display-name>user:John Smith (requires Graph API)
Group/channelconversation:<conversation-id>conversation:19:abc123判...@thread.tacv2
Group/channel (raw)<conversation-id>19:abc123判...@thread.tacv2 (if contains @thread)

CLI examples:

Terminal window
# Send to a user by ID
openclaw message send --channel msteams --target "user:40a1a0ed-..." --message "Hello"
# Send to a user by display name (triggers Graph API lookup)
openclaw message send --channel msteams --target "user:John Smith" --message "Hello"
# Send to a group chat or channel
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello"
# Send an Adaptive Card to a conversation
openclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" \
--card '{"type":"AdaptiveCard","version":"1.5","body":[{"type":"TextBlock","text":"Hello"}]}'

Agent tool examples:

{
action: "send",
channel: "msteams",
target: "user:John Smith",
message: "Hello!",
}
{
action: "send",
channel: "msteams",
target: "conversation:19:abc...@thread.tacv2",
card: {
type: "AdaptiveCard",
version: "1.5",
body: [{ type: "TextBlock", text: "Hello" }],
},
}

Keep in mind that without the user: prefix, names default to group or team resolution. You should always use user: when targeting people by display name.

You can only send proactive messages after a user has interacted with the bot. This is because the system stores conversation references at that specific point. If you need to manage how direct messages work or handle access control, check out /gateway/configuration for details on dmPolicy and allowlist gating.

This is a point that often trips people up: the groupId query parameter you see in Teams URLs is NOT the team ID you use for configuration. You need to extract the correct IDs from the URL path instead.

Team URL:

https://teams.microsoft.com/l/team/19%3ABk4j...%40thread.tacv2/conversations?groupId=...
└────────────────────────────┘
Team ID (URL-decode this)

Channel URL:

https://teams.microsoft.com/l/channel/19%3A15bc...%40thread.tacv2/ChannelName?groupId=...
└─────────────────────────┘
Channel ID (URL-decode this)

When you are setting up your config, follow these rules:

  • The Team ID is the path segment found after /team/. You must URL-decode this (for example, 19:Bk4j...@thread.tacv2).
  • The Channel ID is the path segment found after /channel/, which also needs to be URL-decoded.
  • You should completely ignore the groupId query parameter.
  • Use these decoded strings directly in your configuration files.

You might find that bots have a bit of a hard time in private channels. It’s not a full “no,” but there are definitely some limits you need to keep in mind. Here is how the features compare:

FeatureStandard ChannelsPrivate Channels
Bot installationYesLimited
Real-time messages (webhook)YesMay not work
RSC permissionsYesMay behave differently
@mentionsYesIf bot is accessible
Graph API historyYesYes (with permissions)

Workarounds if private channels don’t work:

  • Try using standard channels for your bot interactions or stick to DMs, as users can always message the bot directly in those spaces.
  • Use the Graph API for historical access, which requires the ChannelMessage.Read.All permission.

Building for Teams can be tricky, and you’ll likely run into a few bumps along the way. Here is how to handle the most frequent headaches.

  • Images not showing in channels: This usually happens when Graph permissions or admin consent are missing. You should reinstall the Teams app and then fully quit and reopen Teams to fix it.
  • No responses in channel: Mentions are required by default. You can set channels.msteams.requireMention=false or configure this specifically for each team or channel.
  • Version mismatch (Teams still shows old manifest): If Teams isn’t picking up your changes, remove and re-add the app, then fully quit Teams to force a refresh.
  • 401 Unauthorized from webhook: You’ll see this if you test manually without an Azure JWT. It actually means your endpoint is reachable but the auth failed, so use Azure Web Chat to test it properly.
  • “Icon file cannot be empty”: This means your manifest points to icon files that are 0 bytes. You need to create valid PNG icons—use 32x32 for outline.png and 192x192 for color.png.
  • “webApplicationInfo.Id already in use”: Your app is likely still installed in another team or chat. You’ll need to find and uninstall it first, or just wait about 5-10 minutes for the change to spread.
  • “Something went wrong” on upload: Try uploading through https://admin.teams.microsoft.com instead. Open your browser DevTools (F12) and check the Network tab response body to see the real error.
  • Sideload failing: If sideloading is blocked, try the “Upload an app to your org’s app catalog” option instead of “Upload a custom app,” as this often gets around those restrictions.
  1. Double-check that webApplicationInfo.id matches your bot’s App ID exactly.
  2. Re-upload the app and reinstall it in the specific team or chat.
  3. Check with your org admin to see if they have blocked RSC permissions.
  4. Make sure you are using the right scope: ChannelMessage.Read.Group for teams or ChatMessage.Read.Chat for group chats.

When you need to get into the weeds of the Microsoft side, these links are where you should go. They cover everything from the initial Azure Bot setup to the specifics of the manifest schema.

You might find these other topics helpful for understanding the broader context. These pages explain how we manage different channels and how the security model works.

OpenClaw

OpenClaw Expert

Still stuck?

If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.