Connecting OpenClaw to Microsoft Teams
Plugin required
Section titled “Plugin required”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:
openclaw plugins install @openclaw/msteamsIf you are running from a git repo, use a local checkout:
openclaw plugins install ./path/to/local/msteams-pluginWhen 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.
Quick setup (beginner)
Section titled “Quick setup (beginner)”Getting started is straightforward. You just need to follow these steps:
- Install the Microsoft Teams plugin.
- Create an Azure Bot to get your App ID and client secret.
- Obtain your tenant ID.
- Add those credentials to your OpenClaw config.
- Make sure
/api/messages(usually port 3978) is reachable via a public URL or tunnel. - 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.
Config writes
Section titled “Config writes”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 } },}Access control (DMs + groups)
Section titled “Access control (DMs + groups)”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 }, }, }, }, }, },}How it works
Section titled “How it works”Getting the integration running follows a clear path:
- Install the Microsoft Teams plugin.
- Create an Azure Bot to get your App ID, secret, and tenant ID.
- Build a Teams app package that points to your bot and includes the necessary RSC permissions.
- Upload and install that Teams app into a team or your personal scope.
- Configure the
msteamssettings in your~/.openclaw/openclaw.jsonfile and start the gateway. - The gateway starts listening for Bot Framework traffic on the
/api/messagesendpoint.
Azure Bot Setup (Prerequisites)
Section titled “Azure Bot Setup (Prerequisites)”You need to set up an Azure Bot resource before you can finish the OpenClaw configuration.
Step 1: Create Azure Bot
Section titled “Step 1: Create Azure Bot”-
Head over to Create Azure Bot
-
Fill out the Basics tab with these settings:
Field Value Bot handle Your bot name, e.g., openclaw-msteams(must be unique)Subscription Select your Azure subscription Resource group Create new or use existing Pricing tier Free for dev/testing Type of App Single Tenant (recommended - see note below) Creation type Create new Microsoft App ID
Deprecation notice: You cannot create new multi-tenant bots after 2025-07-31. Use Single Tenant for all new bots.
- Click Review + create, then hit Create and wait a minute or two.
Step 2: Get Credentials
Section titled “Step 2: Get Credentials”- Open your Azure Bot resource and go to Configuration.
- Copy the Microsoft App ID; this is your
appId. - Click Manage Password to go to the App Registration.
- Under Certificates & secrets, create a New client secret and copy the Value; this is your
appPassword. - Go to the Overview page and copy the Directory (tenant) ID; this is your
tenantId.
Step 3: Configure Messaging Endpoint
Section titled “Step 3: Configure Messaging Endpoint”- Go back to the Configuration section of your Azure Bot.
- Set the Messaging endpoint to your webhook URL:
- For production:
https://your-domain.com/api/messages - For local dev: Use a tunnel URL.
- For production:
Step 4: Enable Teams Channel
Section titled “Step 4: Enable Teams Channel”- In the Azure Bot menu, go to Channels.
- Select Microsoft Teams, click Configure, and hit Save.
- Accept the Terms of Service.
Local Development (Tunneling)
Section titled “Local Development (Tunneling)”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
ngrok http 3978# Copy the https URL, e.g., https://abc123.ngrok.io# Set messaging endpoint to: https://abc123.ngrok.io/api/messagesOption B: Tailscale Funnel
tailscale funnel 3978# Use your Tailscale funnel URL as the messaging endpointTeams Developer Portal (Alternative)
Section titled “Teams Developer Portal (Alternative)”If you want to avoid hand-editing JSON manifests, you can use the Teams Developer Portal instead. It is often a much smoother path:
- Click + New app.
- Fill in the basic info like the name, description, and developer details.
- Go to App features → Bot.
- Select Enter a bot ID manually and paste your Azure Bot App ID.
- Check these scopes: Personal, Team, and Group Chat.
- Click Distribute → Download app package.
- In Teams: Apps → Manage your apps → Upload a custom app → select the ZIP.
Testing the Bot
Section titled “Testing the Bot”Option A: Azure Web Chat (verify webhook first)
- In the Azure Portal, go to your Azure Bot resource and select Test in Web Chat.
- Send a message to see if you get a response.
- This confirms your webhook endpoint works before you deal with Teams-specific setup.
Option B: Teams (after app installation)
- Install the Teams app using sideloading or your org catalog.
- Find the bot in Teams and send a DM.
- Check your gateway logs for incoming activity to confirm the connection.
Setup (minimal text-only)
Section titled “Setup (minimal text-only)”-
Install the Microsoft Teams plugin
- From npm:
openclaw plugins install @openclaw/msteams - From a local checkout:
openclaw plugins install ./path/to/local/msteams-plugin
- From npm:
-
Bot registration
- Create an Azure Bot and make a note of these:
- App ID
- Client secret (App password)
- Tenant ID (single-tenant)
- Create an Azure Bot and make a note of these:
-
Teams app manifest
- Include a
botentry withbotId = <App ID>. - Scopes:
personal,team,groupChat. supportsFiles: trueis required for personal scope file handling.- Add RSC permissions.
- Create icons:
outline.png(32x32) andcolor.png(192x192). - Zip these three files together:
manifest.json,outline.png, andcolor.png.
- Include a
-
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_IDMSTEAMS_APP_PASSWORDMSTEAMS_TENANT_ID
-
Bot endpoint
- Set the Azure Bot Messaging Endpoint to:
https://<host>:3978/api/messages(or your chosen path/port).
- Set the Azure Bot Messaging Endpoint to:
-
Run the gateway
- The Teams channel starts automatically when the plugin is installed and the
msteamsconfig exists with valid credentials.
- The Teams channel starts automatically when the plugin is installed and the
Member info action
Section titled “Member info action”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.GroupRSC permission (this is already in the recommended manifest).- For cross-team lookups:
User.Read.AllGraph Application permission with admin consent.
The action is gated by channels.msteams.actions.memberInfo and is enabled by default when Graph credentials are available.
History context
Section titled “History context”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.
Current Teams RSC Permissions (Manifest)
Section titled “Current Teams RSC Permissions (Manifest)”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 @mentionChannelMessage.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
Example Teams Manifest (redacted)
Section titled “Example Teams Manifest (redacted)”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" }, ], }, },}Manifest caveats (must-have fields)
Section titled “Manifest caveats (must-have fields)”- The
bots[].botIdandwebApplicationInfo.idmust both match your Azure Bot App ID. - Ensure
bots[].scopesincludes every surface you plan to use, such aspersonal,team, orgroupChat. - You must set
bots[].supportsFiles: trueto handle files in the personal scope. - Your
authorization.permissions.resourceSpecificmust include channel read and send permissions if you want to process channel traffic.
Updating an existing app
Section titled “Updating an existing app”If you need to update an app that is already installed—for example, to add RSC permissions—follow these steps:
- Update your
manifest.jsonwith your new settings. - Increment the
versionfield, such as moving from1.0.0to1.1.0. - Re-zip the manifest along with your icons (
manifest.json,outline.png,color.png). - Upload the new zip file through the Teams Admin Center or by sideloading a custom app in Teams.
- For team channels, you must reinstall the app in each team for the new permissions to take effect.
- Fully quit and relaunch the Teams client to clear out any cached app metadata.
Capabilities: RSC only vs Graph
Section titled “Capabilities: RSC only vs Graph”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.
RSC vs Graph API
Section titled “RSC vs Graph API”| Capability | RSC Permissions | Graph API |
|---|---|---|
| Real-time messages | Yes (via webhook) | No (polling only) |
| Historical messages | No | Yes (can query history) |
| Setup complexity | App manifest only | Requires admin consent + token flow |
| Works offline | No (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.
- 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.AllorChatMessage.Read.All(for group chats)
- Make sure to grant admin consent for the tenant.
- Update your Teams app manifest version, re-upload it, and reinstall the app in Teams.
- 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.
Known Limitations
Section titled “Known Limitations”Webhook timeouts
Section titled “Webhook timeouts”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.
Formatting
Section titled “Formatting”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).
Configuration
Section titled “Configuration”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(default3978)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) ornewlineto 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).toolsBySenderkeys should use explicit prefixes:id:,e164:,username:,name:(legacy unprefixed keys still map toid: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).
Routing & Sessions
Section titled “Routing & Sessions”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>
Reply Style: Threads vs Posts
Section titled “Reply Style: Threads vs Posts”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.
| Style | Description | Recommended replyStyle |
|---|---|---|
| Posts (classic) | Messages appear as cards with threaded replies underneath | thread (default) |
| Threads (Slack-like) | Messages flow linearly, more like Slack | top-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
threadin a Threads-style channel makes replies look nested and awkward. - Using
top-levelin 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", }, }, }, }, }, },}Attachments & Images
Section titled “Attachments & Images”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-filewithmedia,filePath, orpath. You can use the optionalmessagefield for text andfilenameto 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.
Sending files in group chats
Section titled “Sending files in group chats”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:
| Context | How files are sent | Setup needed |
|---|---|---|
| DMs | FileConsentCard → user accepts → bot uploads | Works out of the box |
| Group chats/channels | Upload to SharePoint → share link | Requires sharePointSiteId + Graph permissions |
| Images (any context) | Base64-encoded inline | Works out of the box |
Why group chats need SharePoint
Section titled “Why group chats need SharePoint”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.
-
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.
-
Grant admin consent for your tenant.
-
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" -
Configure OpenClaw:
{channels: {msteams: {// ... other config ...sharePointSiteId: "contoso.sharepoint.com,guid1,guid2",},},}
Sharing behavior
Section titled “Sharing behavior”| Permission | Sharing behavior |
|---|---|
Sites.ReadWrite.All only | Organization-wide sharing link (anyone in org can access) |
Sites.ReadWrite.All + Chat.Read.All | Per-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.
Fallback behavior
Section titled “Fallback behavior”| Scenario | Result |
|---|---|
Group chat + file + sharePointSiteId configured | Upload to SharePoint, send sharing link |
Group chat + file + no sharePointSiteId | Attempt OneDrive upload (may fail), send text only |
| Personal chat + file | FileConsentCard flow (works without SharePoint) |
| Any context + image | Base64-encoded inline (works without SharePoint) |
Files stored location
Section titled “Files stored location”Any files you upload are stored in a /OpenClawShared/ folder inside the default document library of your configured SharePoint site.
Polls (Adaptive Cards)
Section titled “Polls (Adaptive Cards)”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.
Adaptive Cards (arbitrary)
Section titled “Adaptive Cards (arbitrary)”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:
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.
Target formats
Section titled “Target formats”MSTeams targets use prefixes to help you distinguish between users and conversations:
| Target type | Format | Example |
|---|---|---|
| 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/channel | conversation:<conversation-id> | conversation:19:abc123判...@thread.tacv2 |
| Group/channel (raw) | <conversation-id> | 19:abc123判...@thread.tacv2 (if contains @thread) |
CLI examples:
# Send to a user by IDopenclaw 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 channelopenclaw message send --channel msteams --target "conversation:19:abc...@thread.tacv2" --message "Hello"
# Send an Adaptive Card to a conversationopenclaw 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.
Proactive messaging
Section titled “Proactive messaging”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.
Team and Channel IDs (Common Gotcha)
Section titled “Team and Channel IDs (Common Gotcha)”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
groupIdquery parameter. - Use these decoded strings directly in your configuration files.
Private Channels
Section titled “Private Channels”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:
| Feature | Standard Channels | Private Channels |
|---|---|---|
| Bot installation | Yes | Limited |
| Real-time messages (webhook) | Yes | May not work |
| RSC permissions | Yes | May behave differently |
| @mentions | Yes | If bot is accessible |
| Graph API history | Yes | Yes (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.Allpermission.
Troubleshooting
Section titled “Troubleshooting”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.
Common issues
Section titled “Common issues”- 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=falseor 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.
Manifest upload errors
Section titled “Manifest upload errors”- “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.pngand 192x192 forcolor.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.
RSC permissions not working
Section titled “RSC permissions not working”- Double-check that
webApplicationInfo.idmatches your bot’s App ID exactly. - Re-upload the app and reinstall it in the specific team or chat.
- Check with your org admin to see if they have blocked RSC permissions.
- Make sure you are using the right scope:
ChannelMessage.Read.Groupfor teams orChatMessage.Read.Chatfor group chats.
References
Section titled “References”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.
- Create Azure Bot - Azure Bot setup guide
- Teams Developer Portal - create/manage Teams apps
- Teams app manifest schema
- Receive channel messages with RSC
- RSC permissions reference
- Teams bot file handling (channel/group requires Graph)
- Proactive messaging
Related
Section titled “Related”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.
- Channels Overview — all supported channels
- Pairing — DM authentication and pairing flow
- Groups — group chat behavior and mention gating
- Channel Routing — session routing for messages
- Security — access model and hardening
OpenClaw Expert
Still stuck?
If this page didn't answer your case, ask OpenClaw Expert for step-by-step guidance.