diff --git a/msteams-platform/TOC.yml b/msteams-platform/TOC.yml index 44d8325b55b..f14675708c3 100644 --- a/msteams-platform/TOC.yml +++ b/msteams-platform/TOC.yml @@ -276,7 +276,315 @@ displayName: loop - name: Action extensions href: resources/messaging-extension-v3/create-extensions.md -- name: Manifest for agents or apps +- name: Build bots and agents + expand: true + items: + - name: Overview + href: bots/overview.md + displayName: AI bot, bot capability, command bot, workflow bot, custom engine agent, agent, conversational bot, activity handler, bot logic, notification bot + - name: Bots and agents in Teams + href: bots/build-a-bot.md + displayName: Bot samples, Code sample, Teams AI library, Microsoft Bot Framework SDK, build a bot, build AI bot, command bot, workflow bot, custom engine agent, agent, conversational bot, notification bot, tools, platforms, Azure OpenAI, Microsoft 365 Agents Toolkit, Azure AI Foundry + - name: Custom engine agent overview + href: /microsoft-365-copilot/extensibility/overview-custom-engine-agent?toc=/microsoftteams/platform/toc.json&bc=/microsoftteams/platform/breadcrumb/toc.json + displayName: AI bot, Teams AI library, custom agent + - name: Teams AI library + items: + - name: Get started + href: /microsoftteams/platform/teams-ai-library/welcome + - name: Bot user experience + items: + - name: Custom engine agent user experience + href: bots/how-to/teams-conversational-ai/ai-ux.md + displayName: AI bot user experience, AI bot UX, custom engine agent, AI label, citation, feedback loop + - name: Stream bot messages + href: bots/streaming-ux.md + displayName: Streaming, response generation, AI library, AI SDK, REST API + - name: Enhance AI-generated bot messages + href: bots/how-to/bot-messages-ai-generated-content.md + displayName: AI label, citations, feedback buttons, sensitivity label, Powered by AI UX Kit + - name: Understand bot concepts + href: bots/bot-concepts.md + displayName: Microsoft Bot Framework SDK, Activity handler, Bot logic, Teams handler + - name: Send and receive messages + items: + - name: Build conversational capability + href: bots/build-conversational-capability.md + displayName: chat, Messages sent between your Microsoft Teams bot and one or more user, Teams channel data, RSC, conversation with RSC + - name: Receive all messages for bots and agents + href: bots/how-to/conversations/channel-messages-for-bots-and-agents.md + - name: Channel and group chat conversations with a bot + href: bots/how-to/conversations/channel-and-group-conversations.md + displayName: Teams conversational bots, mention, tag mention, user mention + - name: Create a commands menu + href: bots/how-to/create-a-bot-commands-menu.md + displayName: compose + - name: Conversation events in your Teams bot + href: bots/how-to/conversations/subscribe-to-conversation-events.md + displayName: conversation bot, activity, Message reaction events, event handling for install and uninstall events, customize bot conversation, bot conversation events, update bot conversation events + - name: Format the bot messages + href: bots/how-to/format-your-bot-messages.md + displayName: cross-platform support + - name: Send and receive files + href: bots/how-to/bots-filesv4.md + displayName: receive files using bot + - name: Send notifications + items: + - name: Build notification capability + href: bots/build-notification-capability.md + displayName: Adapter, notifications + - name: Send proactive messages + href: bots/how-to/conversations/send-proactive-messages.md + displayName: conversationId, send message, bot framework, notification, + - name: Command bot in Teams + href: bots/how-to/conversations/command-bot-in-teams.md + displayName: bot initialization + - name: Create prompt suggestions + href: bots/how-to/conversations/prompt-suggestions.md + displayName: prompt starter, suggested actions + - name: Workflow bot in Teams + href: bots/how-to/conversations/workflow-bot-in-teams.md + displayname: Jira, jira + - name: Get Teams-specific context + href: bots/how-to/get-teams-context.md + displayName: Fetch roster or user profile, get user profile, get roster, get team, get channel, get chat + - name: Configure bot scope + href: bots/how-to/bot-configuration-experience.md + displayName: configuration settings, fetchTask, config/fetch, config/submit + - name: Optimize bots with rate limits + href: bots/how-to/rate-limit.md + displayName: handle rate limit, detect transient exceptions, per thread limit, exponential backoff + - name: Apps for shared and private channels + href: build-apps-for-shared-private-channels.md + displayName: shared channels, private channels + - name: Explore advanced bot capabilities + items: + - name: Modify Teams bot API to fetch team or chat members + href: resources/team-chat-member-api-changes.md + displayName: team, chat, member, API, bot, fetch, team or chat members + - name: Calls and meetings bots + items: + - name: Overview + href: bots/calls-and-meetings/calls-meetings-bots-overview.md + - name: Real-time media calls and meetings + href: bots/calls-and-meetings/real-time-media-concepts.md + displayName: active and dominant speakers, SILK and G.722 for audio and H.264 for video, codec + - name: Register calls and meetings bot + href: bots/calls-and-meetings/registering-calling-bot.md + displayName: How Bot can create call, join meeting and transfer call + - name: Incoming call notifications + href: bots/calls-and-meetings/call-notifications.md + displayName: Redirects for region affinity, GeoDNS resolution + - name: Application-hosted media bots + href: bots/calls-and-meetings/requirements-considerations-application-hosted-media-bots.md + displayName: Service Fabric with Virtual Machine Scale Sets, instance-level public +- name: Build tabs + items: + - name: Tabs overview + href: tabs/what-are-tabs.md + displayName: Webpages embedded in Teams, declare custom tab in app manifest, contentUrl, web apps, device permissions for tabs + - name: Prerequisites + href: tabs/how-to/tab-requirements.md + displayName: clickjacking, device permissions for tabs + - name: Create a tab + href: tabs/how-to/create-personal-tab.md + displayName: App generator, Reorder static personal tabs, channel, or group custom tab, offline tab + - name: Get context for your tab + href: tabs/how-to/access-teams-context.md + displayName: getContext, entityId, groupId, User context, authentication flow from Microsoft Entra ID, retrieve context, theme changes + - name: Tab features + items: + - name: Open content in multi-window + href: tabs/open-content-in-stageview.md + displayName: Tab in Stageview, Collaborative Stageview, Stageview Multi-window, Stageview Modal, Teams multiwindow + - name: Create a configurable tab + href: tabs/how-to/create-channel-group-tab.md + displayName: create a channel tab or group tab, create a custom channel or group tab with ASP.NET Core, build app using Blazor + - name: Create a content page + href: tabs/how-to/create-tab-pages/content-page.md + displayName: Loading indicator, showLoadingIndicator, personal-scoped custom tab, channel or group custom tab + - name: Create a configuration page + href: tabs/how-to/create-tab-pages/configuration-page.md + displayName: configurationUrl + - name: Create a removal page + href: tabs/how-to/create-tab-pages/removal-page.md + displayName: Reconfigure tab, register remove handler + - name: Tabs on mobile + href: tabs/design/tabs-mobile.md + displayName: Teams Store apps approved by Microsoft for mobile + - name: Apps for shared channels + href: concepts/build-and-test/shared-channels.md + displayName: Cross-tenant notifications, context for shared channel, apps and permissions in shared channels + - name: Apps for shared and private channels + href: build-apps-for-shared-private-channels.md + displayName: shared channels, private channels + - name: Send activity feed notifications + href: /graph/teams-send-activityfeednotifications?toc=/microsoftteams/platform/toc.json&bc=/microsoftteams/platform/breadcrumb/toc.json + displayName: activity feed, notification, customize notification + - name: App caching for your tab app + href: tabs/how-to/app-caching.md + displayName: App caching, caching, launch time, cache + - name: Build a dashboard tab app + href: tabs/how-to/Build-a-dashboard-tab-app.md + displayName: Dashboard, widget, Graph API call +- name: Build message extensions + items: + - name: What are message extensions? + href: messaging-extensions/what-are-messaging-extensions.md + displayName: handleTeamsMessagingExtensionFetchTask, commands + - name: Build message extensions using API + expanded: false + items: + - name: API-based message extensions + href: messaging-extensions/api-based-overview.md + displayName: OpenAPI Description, Search, single parameter, search based message extension + - name: Create API-based message extensions + href: messaging-extensions/create-api-message-extension.md + displayName: API-based, Microsoft 365 Agents Toolkit CLI, Developer Portal, Visual Studio Code, Visual Studio + - name: Build message extensions using Bot Framework + items: + - name: Bot-based message extensions + href: messaging-extensions/build-bot-based-message-extension.md + displayName: one-on-one conversation, search command, action command, search based message extension, action based message extension + - name: Action commands + items: + - name: Define action commands + href: messaging-extensions/how-to/action-commands/define-action-command.md + displayName: Command invoke locations, static list of parameters, action command manually + - name: Create and send dialogs + href: messaging-extensions/how-to/action-commands/create-task-module.md + displayName: Initial invoke request, respond to fetchTask, payload activity, handleTeamsMessagingExtensionFetchTask + - name: Respond to the dialog submit action + href: messaging-extensions/how-to/action-commands/respond-to-task-module-submit.md + displayName: Compose, Respond to initial submit action, configure the poll, attribution for bot messages, submit action + - name: Search commands + items: + - name: Define search commands + href: messaging-extensions/how-to/search-commands/define-search-command.md + displayName: bot-based message extensions + - name: Respond to search commands + href: messaging-extensions/how-to/search-commands/respond-to-search.md + displayName: response card types + - name: Universal actions for search based message extensions + href: messaging-extensions/how-to/search-commands/universal-actions-for-search-based-message-extensions.md + displayName: Handle tap actions, Just-in-time install, JIT + - name: Bot-based message extension agent guidelines + href: messaging-extensions/dev-guidelines-agents.md + displayName: criteria, extend message extension, agent, Microsoft 365 Copilot, Copilot agents, plugins + - name: Extend bot-based message extension as agent + href: messaging-extensions/build-bot-based-agent.md + displayName: extend message extension, search based message extension, M365, Microsoft 365 Copilot + - name: Debug an agent for Microsoft 365 Copilot with developer mode + href: /microsoft-365-copilot/extensibility/debugging-copilot-plugin?toc=/microsoftteams/platform/toc.json&bc=/microsoftteams/platform/breadcrumb/toc.json + displayName: Copilot, Microsoft 365 Copilot, enabled agents, matched functions, selected functions for execution, function execution details, plugin + - name: Copilot handoff + href: bots/how-to/conversations/bot-copilot-handoff.md + displayName: custom agent, custom engine agent, agent, conversation, Microsoft 365 Copilot, plugins + - name: Add link unfurling + href: messaging-extensions/how-to/link-unfurling.md + displayName: domain listing, wildcards, cache + - name: Micro-capabilities for website links + href: messaging-extensions/how-to/micro-capabilities-for-website-links.md + displayName: App less link unfurling +- name: Build apps for Teams meetings and calls + items: + - name: Apps for meetings and calls + href: apps-in-teams-meetings/teams-apps-in-meetings.md + displayName: apps in meetings, meeting lifecycle, meeting types in Teams, guest user, in-tenant user, federated or external user, anonymous user, scenes for meetings, meeting participants, app caching in Teams meeting + - name: Enable and configure apps for Teams meetings and calls + items: + - name: Build tabs for meeting + href: apps-in-teams-meetings/build-tabs-for-meeting.md + displayName: anonymous + - name: Build tabs for calling + href: apps-in-teams-meetings/build-tabs-for-calling.md + displayName: PSTN, calling extensibility, calls in Teams + - name: Build apps for meeting stage + href: apps-in-teams-meetings/build-apps-for-teams-meeting-stage.md + displayName: apps for meeting, share content, share to stage APIs, in-meeting document signing, meeting side panel, customize share button + - name: Share in meeting + href: concepts/build-and-test/share-in-meeting.md + displayName: generate a deep link + - name: Build in-meeting notifications + href: apps-in-teams-meetings/in-meeting-notification-for-meeting.md + displayName: bubble, targeted meeting, in-meeting, notification + - name: Enable app icon badging for Teams app + href: apps-in-teams-meetings/app-icon-badging-for-your-app.md + displayName: app icon badging + - name: Extensible conversations for meeting chat + href: apps-in-teams-meetings/build-extensible-conversation-for-meeting-chat.md + - name: Build apps for anonymous users + href: apps-in-teams-meetings/build-apps-for-anonymous-user.md + displayName: supportsAnonymousGuestUsers, guest, meetingExtensionDefinition + - name: Meeting apps APIs + href: apps-in-teams-meetings/meeting-apps-apis.md + displayName: OnMessageActivityAsync, payload, recurring, getparticipant, captions, targeted meeting notification, real-time meeting events, meeting start and end events + - name: Get meeting transcripts, recordings, and AI summaries + items: + - name: Meeting transcripts and recordings + href: graph-api/meeting-transcripts/overview-transcripts.md + displayName: get teams meeting transcripts, get teams meeting recordings, teams automatic meeting recording + - name: Get change notifications + href: /graph/teams-changenotifications-callrecording-and-calltranscript?toc=/microsoftteams/platform/toc.json&bc=/microsoftteams/platform/breadcrumb/toc.json + - name: Get transcripts + href: /graph/api/resources/calltranscript?toc=/microsoftteams/platform/toc.json&bc=/microsoftteams/platform/breadcrumb/toc.json + - name: Get recordings + href: /graph/api/resources/callrecording?toc=/microsoftteams/platform/toc.json&bc=/microsoftteams/platform/breadcrumb/toc.json + - name: Get AI-generated meeting summaries + href: graph-api/meeting-transcripts/meeting-insights.md + displayName: get AI insights, get meeting insights, get meeting AI insights, meeting AI insights API + - name: Enhanced collaboration with Live Share + items: + - name: Live Share SDK + href: apps-in-teams-meetings/teams-live-share-overview.md + displayName: Loop, Fluid Framework, Azure Fluid Relay, distributed data structures, web sockets + - name: Getting started + items: + - name: Quick start guide + href: apps-in-teams-meetings/teams-live-share-quick-start.md + - name: Dice Roller tutorial + href: apps-in-teams-meetings/teams-live-share-tutorial.md + - name: Core capabilities + href: apps-in-teams-meetings/teams-live-share-capabilities.md + displayName: Live Share data structures, broadcast + - name: Media synchronization + href: apps-in-teams-meetings/teams-live-share-media-capabilities.md + displayName: Media synchronization, audio ducking + - name: Canvas + href: apps-in-teams-meetings/teams-live-share-canvas.md + displayName: Inking, cursors, live canvas + - name: How-to guides + items: + - name: Agile Poker tutorial + href: sbs-teams-live-share.yml + - name: Custom Azure Fluid Relay service + href: apps-in-teams-meetings/teams-live-share-how-to/how-to-custom-azure-fluid-relay.md + displayName: access token + - name: Live Share FAQ + href: apps-in-teams-meetings/teams-live-share-faq.md + - name: Custom Together Mode scenes in Teams + href: apps-in-teams-meetings/teams-together-mode.md + displayName: Single virtual scene, scene studio + - name: Integrate meetings and calls in external apps + href: get-started/b2c-apps.md + displayName: Teams meeting, Teams call, Teams meeting and call for external apps, external apps +- name: Build webhooks and connectors + items: + - name: Overview + href: webhooks-and-connectors/what-are-webhooks-and-connectors.md + displayName: Webhooks, Connectors, endpoint, message in channel, Teams message + - name: Create Outgoing Webhooks + href: webhooks-and-connectors/how-to/add-outgoing-webhook.md + displayName: HMAC security token, message in channel, Teams message + - name: Create Incoming Webhooks (Deprecated) + href: webhooks-and-connectors/how-to/add-incoming-webhook.md + displayName: share content in Microsoft Teams channels, external applications, message in channel, Teams message, incoming webhook + - name: Create connectors for Microsoft 365 Groups (Deprecated) + href: webhooks-and-connectors/how-to/connectors-creating.md + - name: Create and send messages (Deprecated) + href: webhooks-and-connectors/how-to/connectors-using.md + displayName: MessageCard, Attachments, potentialAction, Send messages using cURL and PowerShell, Rate limiting for connectors, ActionCard, message in channel, Teams message +- name: Build cards and dialogs items: - name: Manifest schema for agents or apps href: resources/schema/manifest-schema.md diff --git a/msteams-platform/build-apps-for-shared-private-channels.md b/msteams-platform/build-apps-for-shared-private-channels.md new file mode 100644 index 00000000000..a3e005d60fa --- /dev/null +++ b/msteams-platform/build-apps-for-shared-private-channels.md @@ -0,0 +1,664 @@ +--- +title: Teams connect shared and private channels +author: surbhigupta +description: Learn about apps for shared and private channels to securely collaborate with internal and external users in a shared space. +ms.author: surbhigupta +ms.localizationpriority: high +ms.topic: conceptual +ms.date: 04/09/2025 +--- + +# Apps for shared and private channels + +> [!NOTE] +> +> Apps in shared and private channel are currently in [Public developer preview](resources/dev-preview/developer-preview-intro.md). + +Shared and private channels in Microsoft Teams enable flexible collaboration within teams and across organizations. Currently, Bot and tab apps are supported in shared and private channels. With this update, you can experience multiple benefits: + +* **Shared channels**: Allow seamless communication with internal or external members, without changing the user’s context. These channels ensure secure granular access control and real-time membership syncing. + +* **Private channels**: Provide secure space for selected team members to collaborate on sensitive or confidential content, ensuring privacy and focused discussions within the team. + +## Understand channels for app integration + +When you're building or integrating apps with Microsoft Teams, understanding channel types is crucial, as different channels determine app visibility, user access, and data storage behavior: + +| Channels | Access | Collaboration | File storage location | +|----------|----------------------------------|--------------------------------------------------------------------------------|-------------------------------------| +| Standard | All team members by default | Ideal for team-wide collaboration where bots or tabs must be available to everyone | Team’s SharePoint site | +| Private | Only to selected team members | Suitable for scenarios requiring restricted access to bots, connectors, or files | Private channel’s SharePoint site | +| Shared | Cross-team and cross-organization | Enables interaction with users outside the host team without requiring them to join the team | Shared channel’s SharePoint site | + +### Capabilities across channels + +Here’s an outline of the different channels and their capabilities, across various parameters: + +| Model | Channel capabilities | Standard channel | Shared and private channels | +|---------------|------------------------------------------------------------------|-------------------------------------------|-------------------------------------------------------| +| **Membership**| Can add people to the channel without adding to the host team | ❌ | ✔️
(not supported for private channels) | +| | Channel membership can be limited to a subset of the host team | ❌ | ✔️ | +| | Channel can be shared with other teams to inherit members | ❌ | ✔️
(not supported for private channels) | +| | Channel can be shared directly with its parent team | N/A | ✔️
(not supported for private channels) | +| | External users can participate in the channel | ✔️
(B2B collaboration users) | ✔️ | +| | Channel is hosted under a host team | ✔️ | ✔️ | +| **Storage** | Each channel has a dedicated SharePoint site | ❌
(inherits team site) | ✔️ | +| **App Model** | App must be installed in the host team | ✔️ | ✔️ | +| | App installed to host team automatically available in channel | ✔️ | ❌ | +| | App must be added to each channel | ❌ | ✔️ | + +> [!IMPORTANT] +> +> Check your app’s capabilities such as membership boundaries, storage location, and external access. Don't make any code changes, based on channel type. + +### Understand how different channels determine app functionality + +Ensure that you understand that how different channels determine app functionality, membership, storage, or privacy, else can lead to broken functionality or unintended data exposure: + +* **Use channel-specific membership APIs** + + Don't assume that team membership is equal to channel membership. Only members who are added to the channel can participate in shared and private channels. + +* **Distinguish between users and roles** + + Channel members might include in-tenant users, guests, or cross-tenant users (external users from other tenants). If your app needs to distinguish between various users to manage access, data visibility, and feature availability, then you must validate user roles and tenant IDs before granting permissions. + +* **Don't assume a single SharePoint site tied to a team** + + Unlike standard channels, which share SharePoint site with the team, Private and shared channels have their own SharePoint sites. Always use the correct URL for each channel, to avoid missing files or unauthorized access errors. + +* **Keep data scoped to channels** + + Aggregate or cross-post data across channels only when necessary, to prevent accidental leaks. For example, analytics apps shouldn't include private channel data in team-wide reports unless permissions are clearly defined. + +[Back to Top](#apps-for-shared-and-private-channels) + +## Enable apps for shared and private channels + +Most apps can support shared and private channels with a simple manifest update. Based on either of the following scenarios, you can decide the approach: + +* [Apps with no dependence on specified parameters](#apps-with-no-dependence-on-specified-parameters) +* [Apps with dependence on specified parameters](#apps-with-dependence-on-specified-parameters) + +### Apps with no dependence on specified parameters + +If your app doesn’t: + +* Use channel or team membership to determine message delivery, task assignment, or permissions +* Access or manage files stored in Teams or SharePoint +* Combine or share data across multiple channels or teams +* Customize experience, based on users (internal, guests, or external members) + +Then, you only need to: + +1. Add `supportsChannelFeatures`: `tier1` to your app manifest +2. Verify expected behavior, and test your app across channels + +There's no dependence on classical and admin access for `supportsChannelFeatures`: `tier1`. + +### Apps with dependence on specified parameters + +If your app handles advanced scenarios, or depends on the specified parameters listed in the [Apps with no dependence on specified parameters](#apps-with-no-dependence-on-specified-parameters) section, then read through this guide for targeted updates and the best practices. Don't rewrite your code. + +> [!NOTE] +> +> * Tab and bot apps in shared and private channels are available in [Government Community Cloud (GCC), GCC High, Department of Defense (DoD)](concepts/cloud-overview.md), and [Teams operated by 21Vianet](concepts/sovereign-cloud.md) environments. +> * SharePoint and the SharePoint pages apps aren't supported for shared channels in GCC, GCC High, DoD, and Teams operated by 21Vianet environments. + +### Get context for shared and private channels + +When loading the user experience in a shared or private channel, use the data received from the `getContext` call for shared or private channels. The `getContext` call publishes two new properties, `hostTeamGroupID` and `hostTenantID`, which are used to retrieve channel membership using Microsoft Graph APIs. `hostTeam` is the team that creates both private and shared channels. For more information, see [Get context in shared channels](tabs/how-to/access-teams-context.md#get-context-in-shared-channels) and [Get context for your tab for private channels](tabs/how-to/access-teams-context.md#retrieve-context-in-private-channels). + +### Manage channel membership + +Use the `allMembers` API that manages and monitors channel memberships across standard, shared, and private channels. It enhances accuracy by reflecting direct and indirect members correctly. For more information, see [List allMembers](/graph/api/channel-list-allmembers?view=graph-rest-1.0&tabs=http&preserve-view=true ). + +```HTTP +GET /teams/{team-id}/channels/{channel-id}/allMembers +``` + +### Identify members + +* **Direct members:** Users who are added directly to the channel, including users from other tenants (cross-tenants). +* **Indirect members:** Users who are members of the team, with which the channel is shared, including teams in the same tenant or in a cross-tenant. + +You can identify whether a member of a shared or private channel is direct or indirect by checking the `@microsoft.graph.originalSourceMembershipUrl` annotation. This property identifies the source of a member’s access the channels: + +|Member Type |Annotation scope | +|---------|---------| +|Direct member | The `@microsoft.graph.originalSourceMembershipUrl` property shows that the user is directly added to the channels | +|Indirect member |The `@microsoft.graph.originalSourceMembershipUrl` property includes a URL that points to the source team and indicates indirect membership. | + + > [!NOTE] + > You might receive duplicate notifications when a member is added to a shared channel. This scenario can happen if the member is already part of the shared channel directly or indirectly. Use the `allMembers` API to view all the direct and indirect members. Ignore the notification if the member already exists, either directly or indirectly. + +### Manage indirect membership across channels + +You can manage indirect membership in channels using the following Microsoft Graph APIs: + +* Use the `allMembers` API to retrieve all users who are members of a specific channel. + + ```HTTP + GET /teams/{team-id}/channels/{channel-id}/allMembers + ``` + +* Use the `doesUserHaveAccess` API to determine whether the user is removed from the channel and can view all user accesses and relevant permissions. Apps with classic application permissions and RSC permissions can use this API. + + ```HTTP + GET /teams/{team-id}/channels/{channel-id}/doesUserHaveAccess(userId='@userid',tenantId='@TenantID',userPrincipalName='@UserPrincipalName') + ``` + +* Use the`sharedWithTeams` API to list all teams a channel is shared with. + + ```HTTP + GET /teams/{team-id}/channels/{channel-id}/sharedWithTeams + ``` + +* Use the `allowedMembers` API to retrieve users from a shared team who can access a shared channel. + + ```HTTP + GET /teams/{team-id}/channels/{channel-id}/sharedWithTeams/{sharewithteamsId}/allowedMembers + ``` + +>[!NOTE] +>The `allowedMembers` API returns only newly associated users and doesn't apply to unshared events. + +[Back to Top](#apps-for-shared-and-private-channels) + +## Get app notifications for graph membership changes + +Apps installed in shared and private channels receive notifications when users are added to or removed from a team that shares the channel. + +To receive app notifications, you must: + +1. Install the app in a host team and enable it for the shared or private channel. For more information on installing the app, see [Install the app](concepts/deploy-and-publish/apps-upload.md). +2. Create a valid Microsoft Graph change notification subscription to monitor associated team membership changes and shared or unshared events using supported APIs. + +To receive both direct and indirect member update notifications, you must include both the query string parameters when creating a subscription. If the query strings aren't provided, the subscription only delivers notifications for direct member updates. For more information, see [Channel membership access] + +```HTTP +/teams/{team-id}/channels/getAllMembers?notifyOnIndirectMembershipUpdate=true&suppressNotificationWhenSharedUnsharedWithTeam=true +``` + +This subscription enables apps to monitor membership changes in channels and its associated teams. For more information on how to create a Microsoft Graph change notification subscription, see [Create a subscription.](/graph/teams-changenotifications-teammembership) + +## Get app notifications for bot membership changes + +The `conversationUpdate` event is sent to your bot when it receives notifications on membership updates for teams where it's added. To receive both direct and indirect member update notifications, configure your bot with the following prerequisites: + +1. Update the app manifest. Add `supportsChannelFeatures`: `tier1` to declare app readiness. + +2. Request Resource-Specific Consent (RSC) permission + + Your app must request the following RSC permission to access channel membership information: + + ```json + { + "authorization": { + "permissions": { + "resourceSpecific": [ + { + "name": "ChannelMember.Read.Group", + "type": "Application" + } + ] + } + } + } + ``` + +3. Ensure the bot is enabled in the shared channel + + To receive member event notifications, install the bot at the team level and manually allow it in the shared channel. + + This process ensures the bot is active and authorized to receive notifications for both direct and indirect members. + +### Manage member added and removed events + +A member added event is sent to your bot in the following scenarios: + +1. When the bot, itself, is installed and added to a conversation +2. When a user is added to a conversation where the bot is installed + +A member removed event is sent to your bot in the following scenarios: + +1. When the bot, itself, is uninstalled and removed from a conversation. +2. When a user is removed from a conversation where the bot is installed. + +For more information, see [Conversation events.](/graph/teams-changenotifications-teammembership) + +If the bot is installed in the team or channel, the Agents SDK receives a `conversationUpdate` activity through the `OnConversationUpdateActivityAsync` method, when a shared channel is added to another team. + +When a new member is added to a shared channel, the ```OnMembersAddedAsync``` method is called. This method provides the context and details of the user who was added, allowing the bot to respond accordingly. + +The following Agents SDK examples apply to both direct and indirect member add and remove events. + +Member added event + +```csharp +public async Task OnMembersAddedAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) +{ + var membersAdded = turnContext.Activity.MembersAdded; + + List addedMembers = new List(); + foreach (var member in membersAdded) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + addedMembers.Add($"Member {member.Name} (ID {member.Id}) added."); + } + } + + await ActivityUtils.SendAdaptiveCard( + "Member Added", + addedMembers, + new List { "membersAdded", membersAdded }, + turnContext, + cancellationToken).ConfigureAwait(false); +``` + +Member removed event + +```csharp +public async Task OnMembersRemovedAsync(ITurnContext turnContext, AppState turnState, CancellationToken cancellationToken) +{ + var membersRemoved = turnContext.Activity.MembersRemoved; + + List removedMembers = new List(); + foreach (var member in membersRemoved) + { + if (member.Id != turnContext.Activity.Recipient.Id) + { + removedMembers.Add($"Member {member.Name} (ID {member.Id}) removed."); + } + } + + await ActivityUtils.SendAdaptiveCard( + "Member Removed", + removedMembers, + new List { "membersRemoved", membersRemoved }, + turnContext, + cancellationToken).ConfigureAwait(false); +} +``` + +--- + +### Handle bulk membership changes + +If there are bulk membership changes, Teams curbs individual membership update notifications when a channel is shared or unshared with a team. To reduce notification overload during membership updates, such as when a shared channel is added to or removed from a team with thousands of members, use the`sharedWithTeams` subscription resource: + +```HTTP +/teams/{team-id}/channels/{channel-id}/sharedWithTeams +``` + +The `sharedWithTeams` subscription sends a single notification when a channel is shared or unshared with a team. It avoids thousands of per-user notifications and improves performance for apps that monitor membership changes. Ensure that you update the shared channel member list using the [allMembers](/graph/api/channel-list-allmembers?view=graph-rest-1.0&tabs=http&preserve-view=true ) API after receiving a *shared with* or *unshared from* team notification. + +## Validate user access for membership updates + +When an app receives a *member removed* notification for an indirect membership update, it’s important to verify whether the user is removed from the channel, especially since the same user might have both direct and indirect membership. For example, if a user is removed from a team that shares a channel, your app must confirm whether the user's access to the shared channel is revoked. Use the `doesUserHaveAccess` API to determine whether the user is removed from the shared channel. See [doesUserHaveAccess](/graph/api/channel-doesuserhaveaccess?view=graph-rest-beta&tabs=http&preserve-view=true ) API to learn more about user accesses and relevant permissions. + +```HTTP +GET /teams/{team-id}/channels/{channel-id}/doesUserHaveAccess(userId='@userid',tenantId='@TenantID',userPrincipalName='@UserPrincipalName') +``` + +When an app receives a *member added* notification for an indirect membership update, see the [allMembers](/graph/api/channel-list-allmembers?view=graph-rest-1.0&tabs=http&preserve-view=true ) API to refresh the list of all members. + +```HTTP +GET /teams/{team-id}/channels/{channel-id}/allMembers +``` + +### Classify members as in-tenant or out-tenant + +You can classify members as in-tenant or out-tenant by comparing the 'TenantId' of the member or team with `ownerTenantId` as follows: + +1. Get the 'TenantId' of the member you wish to compare. + +```HTTP +GET /teams/{host-team-group-id}/channels/{channel-id}/allMembers +``` + +2. Call `microsoftTeams.app.getContext()` in your tab from the Teams JavaScript client library. The getContext() call returns context of the shared channel, which contains the details such as `displayName`, `membershipType`, `ownerGroupId`, and `ownerTenantId`. + +3. Compare the `TenantId` of the member to the `ownerTenantId` property and determine if the member is an in-tenant or out-tenant. + +[Back to Top](#apps-for-shared-and-private-channels) + +## Understand app permissions in shared channels + +You can collaborate with external members outside of your organization using shared channels. App permissions in shared channels follow the host team's app roster and host tenant's app policy. + +> [!NOTE] +> The [activity feed notification API](/graph/teams-send-activityfeednotifications) doesn't support cross-tenant notifications for apps in a shared channel. + +## Verify app addition to a channel + +When a shared channel is added to another team, the Agents SDK receives a `conversationUpdate` activity through the `OnConversationUpdateActivityAsync` method, only if the bot is installed in the team. There’s no dedicated API to check if your app is part of a channel. Bots can detect when your app is added to a channel indirectly. + +Use this `channelMemberAdded` event to trigger app-specific logic such as: + +* Sending a welcome message +* Fetching the channel roster +* Configuring tabs +* Starting scheduled jobs + +```csharp + protected override async Task OnConversationUpdateActivityAsync( + ITurnContext turnContext, + CancellationToken cancellationToken) + { + var tcd = turnContext.Activity.GetChannelData(); + var eventType = tcd?.EventType?.ToLowerInvariant(); + + var extended = turnContext.Activity.GetChannelData(); + + var raw = turnContext.Activity.ChannelData as JObject + ?? (turnContext.Activity.ChannelData != null + ? JObject.FromObject(turnContext.Activity.ChannelData) + : new JObject()); + + _logger.LogInformation("ConversationUpdate eventType={EventType}, channelId={ChannelId}, teamId={TeamId}", + eventType, tcd?.Channel?.Id, tcd?.Team?.Id); + + switch (eventType) + { + case "channelshared": + { + var hostTeam = extended?.Team; + var sharedWith = extended?.SharedWithTeams ?? new List(); + + _logger.LogInformation("ChannelShared: hostTeam={HostTeamId}, sharedWithCount={Count}", + hostTeam?.Id, sharedWith.Count); + + foreach (var team in sharedWith) + { + _logger.LogInformation("SharedWithTeam: id={Id}, name={Name}, aadGroupId={AadGroupId}, tenantId={TenantId}", + team.Id, team.Name, team.AadGroupId, team.TenantId); + } + + await turnContext.SendActivityAsync( + MessageFactory.Text($" Channel shared with {sharedWith.Count} team(s)."), + cancellationToken); + break; + } + + case "channelunshared": + { + var unsharedFrom = extended?.UnsharedFromTeams ?? new List(); + + _logger.LogInformation("ChannelUnshared: unsharedFromCount={Count}", unsharedFrom.Count); + + foreach (var team in unsharedFrom) + { + _logger.LogInformation("UnsharedFromTeam: id={Id}, name={Name}, aadGroupId={AadGroupId}, tenantId={TenantId}", + team.Id, team.Name, team.AadGroupId, team.TenantId); + } + + await turnContext.SendActivityAsync( + MessageFactory.Text($" Channel unshared from {unsharedFrom.Count} team(s)."), + cancellationToken); + break; + } + + default: + break; + } + + await base.OnConversationUpdateActivityAsync(turnContext, cancellationToken); + } +``` + +## Authenticate external users to access app content in SharePoint + +You need to complete this step when your app stores content in the SharePoint site of the tenant that hosts the channel and requests a SharePoint token. + +### [Tabs](#tab/tabs) + +1. Save host tenant ID of shared channel where tab is configured. +2. Retrieve the host tenant ID using `channel.ownerTenantId` in JSv2 or from the `getContext` call in JSv1. + +### [Bots](#tab/bots1) + +To retrieve the host tenant ID for any event or action payload received by a bot, use `turnContext.activity.conversation.tenantId`. + +--- + +Now, send saved host tenantId inside tenantId parameter of getAuthToken call to allow cross-tenant users to access content hosted inside SharePoint site attached to the shared channel. + +## Identify guest users in channels + +You can identify if a member of a channel is a guest user, invited to your tenant from external organization, using `roles` property received for each object in [List members of a channel](/graph/api/channel-list-members) response. + +For guests, 'roles' = 'guest' + +To accurately retrieve the all guest users in a channel, use the following `allMembers` API: + +```HTTP +GET /teams/{team-id}/channels/{channel-id}/allMembers +``` + +This API works across standard and other channels and is recommended for reliably identifying guest members. + +## Access SharePoint data in shared and private channels + +If you're building an app using [SharePoint](/sharepoint/dev/spfx/integrate-with-teams-introduction) Framework, you need to use the SharePoint Online (SPO) site linked to the shared channel, not the one linked to the host team group. Both shared and private channels have their own SPO site that is only accessible to members of that specific shared or private channel. + +Use the Microsoft Graph invite API to access the document library of the SPO site linked to a shared or private channel. + +[Back to Top](#apps-for-shared-and-private-channels) + +### Access SharePoint storage correctly for channel files + +To access a channel’s SharePoint files root, use the following API: + +```HTTP +GET /teams/{teamId}/channels/{channelId}/filesFolder +``` + +This API returns a DriveItem object for that channel's files root. For more, see [channel files](/graph/api/channel-get-filesfolder?view=graph-rest-1.0&tabs=http&preserve-view=true) + +Use the following properties for all subsequent file operations: + +* `parentReference.driveId`: The SharePoint driveId for the channel’s site. +* `itemId`: The folderId for the channel’s root. + +Following is the expected drive behavior of the channels: + +* Standard channels use the team site’s driveId. +* Other channels use a separate driveId for their individual sites. + +> [!NOTE] +> +> Always store and reuse the `driveId` and `itemId` returned by the API. +> Don't hardcode library names or URLs based on assumptions about the team site, as the team site location can change. +> Use this `GET /teams/{teamId}/channels/{channelId}/filesFolder` API for all channel types. + +### Manage file access for external or guest users + +#### [External users](#tab/external-users) + +External users remain in their tenant while accessing the host channel’s sharepoint site. To enable access: + +1. Configure cross-tenant access on both sides. +2. Ensure your app is multitenant and receives consent in the host tenant. + +#### [Guest users](#tab/guest-users) + +The channel’s SharePoint site automatically grants access to all channel members, including tenants. + +Ensure that you: + +* Avoid using organization-wide sharing links they typically exclude external users. +* Use specific-people sharing, or rely on membership-based permissions. +* Check tenant or site policies, as they might block anonymous or organization-wide links. + +To grant access to specific users or groups, use the following API: + +```HTTP +POST /drives/{driveId}/items/{itemId}/invite +``` + +--- + +### Authenticate external users in tabs or task modules + +When your tab or task module needs to access sharepoint resources in the channel’s home tenant, perform the following steps: + +1. Detect external users +Use getContext() to retrieve channel context. Compare `user.tenant.id` with `channel.ownerTenantId or channel.hostTenantId`. If they differ, the user is external. + +2. Request token from home tenant +Call getAuthToken() with the external user's tenant ID (`user.tenant.id` or `tid`) to ensure the token is issued from their home tenant. + +[Back to Top](#apps-for-shared-and-private-channels) + +## Test your app across channels + +Before publishing updates, ensure your app works correctly across all channel types in real situations. + +### Standard channel + +Confirm that the existing functionality remains intact after your changes. Ensure tabs, bots, and messaging extensions continue to work as expected. + +### Shared channel + +### [Shared channel (same tenant)](#tab/sharedchannel) + +Create shared channel in Team A, then share it with Team B (requires owner permissions). + +Perform the following steps to validate: + +1. Add the app to Team A (host team), then to Channel X. +2. Validate that the members from Team B: +Can see the tab and receive bot responses. +1. Unshare the channel from Team B and confirm: + * Your bot receives a `channelUnshared` event + * Membership updates are handled correctly + +### [Shared channel (external tenant)](#tab/sharedchannel-externaltenant) + +Use two tenants or collaborate with a colleague from another organization via Teams Connect. + +Perform the following steps to validate: + +1. Have an external user send a message to your bot and confirm that it responds. The bot must receive the message, provided added to the appropriate scope (personal chat, group chat, or channel). +1. Have the external user trigger a task module or tab interaction and verify that the authentication succeeds. + * If using Single Sign-On authentication (SSO), ensure `getAuthToken` handles the user's home tenant ID correctly. +2. Attempt to send a direct message from your bot to the external user: + * This functionality fails if the user is outside your tenant + * Confirm that in-channel communication still works. + +--- + +### Private channel + +Create a private channel in Team A with atleast two members (owner and member). + +Perform the following steps to validate: + +1. Add the app to Team A then add it to private channel. +2. Verify that your tab loads correctly in the private channel. +3. Test bot responses for different user types: + * In-tenant member + * Guest user or external user +4. If your app lists members or assigns tasks, confirm it only uses channel members and not the complete team. +5. Add a new member to the private channel and check: + * Whether your app receives a membership change event + * Whether your membership API reflects the new member + +Testings across these scenarios help you spot any issues with functionality, permissions, and user experience. + +## Best practices for supporting all channels + +### Dos + +* **Always retrieve the current channel’s member list and roles** before performing actions. For example, when sending notifications or assigning tasks, target only the actual channel members and not the entire team. +* **Control data access and sharing** based on channel membership and permissions. For more information, see [Manage channel membership](#manage-channel-membership). +* **Determine** whether users are internal, guests, or external (cross-tenant), and authenticate them in their home tenant. Always validate permissions for cross-tenant scenarios, especially when accessing files. For more information, see [Identify guest users in channels](#identify-guest-users-in-channels) +* **Update help text and user guides** to explain how your app behaves in different channel types, including any limitations for guests or external users. +* **Use cache large member lists and change notifications** to update them, rather than relying on frequent API calls. For example, refresh your cache only when a membership change event occurs. +* **Review Microsoft Teams documentation and changelogs** to stay aligned with the latest updates to APIs, permissions, and channel configurations. + +### Don'ts + +* **Restrict sensitive actions** to owners or internal users and offer limited features to guests or external participants. +* **Never include private-channel data** in broader reports or public channels unless explicitly authorized. + +## Frequently asked questions + +
+Why isn’t the app visible when trying to add it to a channel? + +The app might not appear if the manifest is missing required support, such as `supportsChannelFeatures: tier1`. Additionally, the installer might not have sufficient permissions, only team members or owners can add apps, and local policies must allow app installation. If the channel is an incoming shared channel (shared into a team), apps can't be added directly from that location. In such cases, switch to the host team to add the app to the channel. You can detect whether a channel is shared-in by checking the channel metadata for the host team ID. +
+  +
+
+Why am I getting a 403 error stating 'app not enabled in this channel' when calling channel APIs? + +This error occurs if the app is installed at the team level but isn't added to the channel. To resolve this issue, confirm that the app is added to the channel. If your app uses resource-specific consent (RSC), verify that the permissions declared in the manifest match the API calls being made, for example, `ChannelMember.Read.Group` for reading channel membership. After adding the app, retry the operation. For bots, initiate channel-specific logic when the bot receives the `channelMemberAdded` event to verify successfully addition to the channel. +
+  +
+
+Why does the channel roster appear incomplete, showing only owners or missing users? + +The channel roster appears incomplete because the team members API is used instead of the correct channel-specific API. To resolve this issue, use the `/channels/{id}/allMembers` API to retrieve the full channel roster. If the response still shows only owners, the app likely isn't added to the channel. Prompt the user to add the app to the channel, then retry the request to fetch the updated roster. +
+  +
+
+Why does file access fail for some users even though they're part of the channel? + +This failure can happen if the app is using the team’s main SharePoint site instead of the channel's specific site. Your organization’s sharing policies might block the type of link, or external users might lack the necessary permissions. To resolve this issue, make sure your app uses the channel’s filesFolder property to get the correct driveId and itemId for file operations. When you're sharing files, use **people with existing access** links or the invite API to give access to specific users or groups. +
+  +
+
+Why are external users experiencing authentication issues in tabs or task modules? + +Authentication issues often occur when the app requests a token for the host tenant instead of the user’s home tenant. To resolve this issue, check whether the user is external by comparing `context.user.tenant.id` with the host or owner tenant ID. If they're different, the user is external, and your app must request the token for the user’s home tenant. You can do this step by passing the correct tenant ID (tid) when calling `getAuthToken`. +
+  +
+
+How do I know my app was added to a channel? + +This issue might occur if the app expects a centralized list of installed apps at the channel level or relies on team-level installation behavior. Currently, there's no channel-level installedApps list available. Instead, bots must listen for the `channelMemberAdded` event within the channel to detect when they're added. When the app gets a 403 error and misses the event, it asks the user to add the bot to the channel and manages the error. +
+  +
+
+Why is my app failing to create message change notifications in shared or private channels? + +Message change notifications might fail in shared or private channels because subscriptions to `/channels/{id}/messages` are blocked when using resource-specific consent (RSC) in these types of channels. If your app receives a 403 error when attempting to create a subscription, this behavior is expected. To resolve this issue, use on-demand message reads after the app is successfully added to the channel. +
+  +
+
+Why do file links still fail for external users even after the app is added to the channel? + +The message change notification failure happens when the tenant’s sharing policy blocks the link type, or when the user doesn’t have access to the item, even if they’re a member of the channel. Another common cause is that the app might generate links pointing to the team drive instead of the channel’s dedicated drive. To resolve this issue, reissue the links using the 'people with existing access' option or use the invite API to grant access to specific users. Also, ensure the links reference the channel drive, which can be identified using the filesFolder property, rather than the team site. +
+  +
+ +[Back to Top](#apps-for-shared-and-private-channels) + +## Code sample + +| Sample Name | Description | .NET | Node.js | Python | +|------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------|---------|--------| +| Teams Conversation Bot | This sample app displays the names of the members in a federated group chat with external users. | NA | View | NA | +| Membership Change Notification | The sample application demonstrates how to send notifications for shared channel events in Microsoft Teams. Scenarios include users being added, removed, or membership being updated and when channel is shared or unshared with a team. | View | View | View | + +## See also + +* [Manage channel membership](#manage-channel-membership) +* [Understand app permissions in shared channels](#understand-app-permissions-in-shared-channels) +* [Build tabs for Teams](tabs/what-are-tabs.md) +* [Shared channels in Microsoft Teams](/microsoftteams/shared-channels) +* [Channel resource type](/graph/api/resources/channel) +* [Retention policy for Teams locations](/microsoft-365/compliance/create-retention-policies) +* [Use guest access and external access to collaborate with people outside your organization](/microsoftteams/communicate-with-users-from-other-organizations) +* [Manage external meetings and chat with people and organizations using Microsoft identities](/microsoftteams/trusted-organizations-external-meetings-chat?tabs=organization-settings) diff --git a/msteams-platform/concepts/build-and-test/shared-channels.md b/msteams-platform/concepts/build-and-test/shared-channels.md index cfa50647ee8..3fa5a204076 100644 --- a/msteams-platform/concepts/build-and-test/shared-channels.md +++ b/msteams-platform/concepts/build-and-test/shared-channels.md @@ -134,4 +134,4 @@ If you're developing an app for use in federated group chats with external users * [Channel resource type](/graph/api/resources/channel) * [Retention policy for Teams locations](/microsoft-365/compliance/create-retention-policies) * [Use guest access and external access to collaborate with people outside your organization](/microsoftteams/communicate-with-users-from-other-organizations) -* [Manage external meetings and chat with people and organizations using Microsoft identities](/microsoftteams/trusted-organizations-external-meetings-chat?tabs=organization-settings) +* [Manage external meetings and chat with people and organizations using Microsoft identities](/microsoftteams/trusted-organizations-external-meetings-chat?tabs=organization-settings) \ No newline at end of file