The code samples in this section are based on version 4.6 and later versions of the Bot Framework SDK. If you are looking for documentation for earlier versions, see the bots - v3 SDK section in the Legacy SDKs folder of the documentation.
When building your conversational bots for Microsoft Teams, you can work with conversation events. Teams sends notifications to your bot for conversation events that happen in scopes where your bot is active. You can capture these events in your code and take the following actions:
Trigger a welcome message when your bot is added to a team.
Trigger a welcome message when a new team member is added or removed.
Trigger a notification when a channel is created, renamed, or deleted.
Trigger a notification when a bot message is liked by a user.
Identify the default channel for your bot from user input (selection) during installation.
The following video demonstrates how a conversation bot can improve customer engagement through smooth, intelligent interactions:
Conversation update events
You can use conversation update events to provide better notifications and effective bot actions.
Important
You can add new events any time and your bot begins to receive them.
You must design your bot to receive unexpected events.
If you are using the Bot Framework SDK, your bot automatically responds with a 200 - OK to any events you choose not to handle.
When an Azure Communication Services (ACS) client joins or leaves the Teams meeting, no conversation update events are triggered.
A bot receives a conversationUpdate event in either of the following cases:
When the bot is added to a conversation.
Other members are added to or removed from a conversation.
Conversation metadata has changed.
The conversationUpdate event is sent to your bot when it receives information on membership updates for teams where it has been added. It also receives an update when it has been added for the first time for personal conversations.
The following table shows a list of Teams conversation update events with more details:
protected override async Task OnTeamsChannelCreatedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{channelInfo.Name} is the Channel created");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
async def on_teams_channel_created(
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
):
# Sends a message activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(
f"The new channel is {channel_info.name}. The channel id is {channel_info.id}"
)
)
Channel renamed
The channelRenamed event is sent to your bot whenever a channel is renamed in a team where your bot is installed.
The following code shows an example of channel renamed event:
protected override async Task OnTeamsChannelRenamedAsync(ChannelInfo channelInfo, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{channelInfo.Name} is the new Channel name");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
async def on_teams_channel_deleted(
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
):
# Sends a message activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(f"The deleted channel is {channel_info.name}")
)
Channel restored
The channelRestored event is sent to your bot, whenever a channel that was previously deleted is restored in a team where your bot is already installed.
The following code shows an example of channel restored event:
async def on_teams_channel_restored(
self, channel_info: ChannelInfo, team_info: TeamInfo, turn_context: TurnContext
):
# Sends a message activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(
f"The restored channel is {channel_info.name}. The channel id is {channel_info.id}"
)
)
Members added
A member added event is sent to your bot in the following scenarios:
When the bot, itself, is installed and added to a conversation
In team context, the activity's conversation.id is set to the id of the channel selected by the user during app installation or the channel where the bot was installed.
When a user is added to a conversation where the bot is installed
User ids received in the event payload are unique to the bot and can be cached for future use, such as directly messaging a user.
The member added activity eventType is set to teamMemberAdded when the event is sent from a team context. To determine if the new member added was the bot itself or a user, check the Activity object of the turnContext. If the MembersAdded list contains an object where id is the same as the id field of the Recipient object, then the member added is the bot, else it's a user. The bot's id is formatted as 28:<MicrosoftAppId>.
Tip
Use the InstallationUpdate event to determine when when your bot is added or removed from a conversation.
The following code shows an example of team members added event:
protected override async Task OnTeamsMembersAddedAsync(IList<TeamsChannelAccount> teamsMembersAdded , TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (TeamsChannelAccount member in teamsMembersAdded)
{
if (member.Id == turnContext.Activity.Recipient.Id)
{
// Send a message to introduce the bot to the team.
var heroCard = new HeroCard(text: $"The {member.Name} bot has joined {teamInfo.Name}");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
else
{
var heroCard = new HeroCard(text: $"{member.Name} joined {teamInfo.Name}");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
}
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
this.onTeamsMembersAddedEvent(async (membersAdded: ChannelAccount[], teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
let newMembers: string = '';
console.log(JSON.stringify(membersAdded));
membersAdded.forEach((account) => {
newMembers += account.id + ' ';
});
const name = !teamInfo ? 'not in team' : teamInfo.name;
const card = CardFactory.heroCard('Account Added', `${newMembers} joined ${name}.`);
const message = MessageFactory.attachment(card);
// Sends a message activity to the sender of the incoming activity.
await turnContext.sendActivity(message);
await next();
});
}
}
The message your bot receives when the bot is added to a team.
Note
In this payload, conversation.id and channelData.settings.selectedChannel.id are the IDs of the channel that the user selected during app installation or from which the installation was triggered.
async def on_teams_members_added(
self, teams_members_added: [TeamsChannelAccount], turn_context: TurnContext
):
for member in teams_members_added:
.. # Sends a message activity to the sender of the incoming activity.
await turn_context.send_activity(
MessageFactory.text(f"Welcome your new team member {member.id}")
)
return
Members removed
A member removed event is sent to your bot in the following scenarios:
When the bot, itself, is uninstalled and removed from a conversation.
When a user is removed from a conversation where the bot is installed.
The member removed activity eventType is set to teamMemberRemoved when the event is sent from a team context. To determine if the new member removed was the bot itself or a user, check the Activity object of the turnContext. If the MembersRemoved list contains an object where id is the same as the id field of the Recipient object, then the member added is the bot, else it's a user. The bot's id is formatted as 28:<MicrosoftAppId>.
Note
When a user is permanently deleted from a tenant, membersRemoved conversationUpdate event is triggered.
The following code shows an example of team members removed event:
protected override async Task OnTeamsMembersRemovedAsync(IList<ChannelAccount> membersRemoved, TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
foreach (TeamsChannelAccount member in membersRemoved)
{
if (member.Id == turnContext.Activity.Recipient.Id)
{
// The bot was removed.
// You should clear any cached data you have for this team.
}
else
{
var heroCard = new HeroCard(text: $"{member.Name} was removed from {teamInfo.Name}");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
}
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
this.onTeamsMembersRemovedEvent(async (membersRemoved: ChannelAccount[], teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
let removedMembers: string = '';
console.log(JSON.stringify(membersRemoved));
membersRemoved.forEach((account) => {
removedMembers += account.id + ' ';
});
const name = !teamInfo ? 'not in team' : teamInfo.name;
const card = CardFactory.heroCard('Account Removed', `${removedMembers} removed from ${teamInfo.name}.`);
const message = MessageFactory.attachment(card);
// Sends a message activity to the sender of the incoming activity.
await turnContext.sendActivity(message);
await next();
});
}
}
The channelData object in the following payload example is based on adding a member to a team rather than a group chat, or initiating a new one-to-one conversation:
async def on_teams_members_removed(
self, teams_members_removed: [TeamsChannelAccount], turn_context: TurnContext
):
for member in teams_members_removed:
..# Sends a message activity to the sender of the incoming activity.
await turn_context.send_activity(
MessageFactory.text(f"Say goodbye to {member.id}")
)
return
Team renamed
Your bot is notified when the team is renamed. It receives a conversationUpdate event with eventType.teamRenamed in the channelData object.
The following code shows an example of team renamed event:
protected override async Task OnTeamsTeamRenamedAsync(TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{teamInfo.Name} is the new Team name");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Bot is notified when the team is renamed.
this.onTeamsTeamRenamedEvent(async (teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
const card = CardFactory.heroCard('Team Renamed', `${teamInfo.name} is the new Team name`);
const message = MessageFactory.attachment(card);
// Sends an activity to the sender of the incoming activity.
await turnContext.sendActivity(message);
await next();
});
}
}
# Bot is notified when the team is renamed.
async def on_teams_team_renamed(
self, team_info: TeamInfo, turn_context: TurnContext
):
# Sends an activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(f"The new team name is {team_info.name}")
)
Team deleted
The bot receives a notification when the team is deleted. It receives a conversationUpdate event with eventType.teamDeleted in the channelData object.
The following code shows an example of team deleted event:
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Invoked when a Team Deleted event activity is received from the connector. Team Deleted corresponds to the user deleting a team.
this.onTeamsTeamDeletedEvent(async (teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
// Handle delete event.
await next();
});
}
}
# Invoked when a Team Deleted event activity is received from the connector. Team Deleted corresponds to the user deleting a team.
async def on_teams_team_deleted(
self, team_info: TeamInfo, turn_context: TurnContext
):
# Handle delete event.
)
Team restored
The bot receives a notification when a team is restored after being deleted. It receives a conversationUpdate event with eventType.teamrestored in the channelData object.
The following code shows an example of team restored event:
protected override async Task OnTeamsTeamrestoredAsync(TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{teamInfo.Name} is the team name");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Invoked when a Team Restored event activity is received from the connector. Team Restored corresponds to the user restoring a team.
this.onTeamsTeamrestoredEvent(async (teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
const card = CardFactory.heroCard('Team restored', `${teamInfo.name} is the team name`);
const message = MessageFactory.attachment(card);
// Sends an activity to the sender of the incoming activity.
await turnContext.sendActivity(message);
await next();
});
}
}
# Invoked when a Team Restored event activity is received from the connector. Team Restored corresponds to the user restoring a team.
async def on_teams_team_restored(
self, team_info: TeamInfo, turn_context: TurnContext
):
# Sends an activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(f"The team name is {team_info.name}")
)
Team archived
The bot receives a notification when the team is installed and archived. It receives a conversationUpdate event with eventType.teamarchived in the channelData object.
The following code shows an example of team archived event:
protected override async Task OnTeamsTeamArchivedAsync(TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{teamInfo.Name} is the team name");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Invoked when a Team Archived event activity is received from the connector. Team Archived.
this.onTeamsTeamArchivedEvent(async (teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
const card = CardFactory.heroCard('Team archived', `${teamInfo.name} is the team name`);
const message = MessageFactory.attachment(card);
// Sends an activity to the sender of the incoming activity.
await turnContext.sendActivity(message);
await next();
});
}
}
# Invoked when a Team Archived event activity is received from the connector. Team Archived correspond to the user archiving a team.
async def on_teams_team_archived(
self, team_info: TeamInfo, turn_context: TurnContext
):
# Sends an activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(f"The team name is {team_info.name}")
)
Team unarchived
The bot receives a notification when the team is installed and unarchived. It receives a conversationUpdate event with eventType.teamUnarchived in the channelData object.
The following code shows an example of team unarchived event:
protected override async Task OnTeamsTeamUnarchivedAsync(TeamInfo teamInfo, ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
var heroCard = new HeroCard(text: $"{teamInfo.Name} is the team name");
// Sends an activity to the sender of the incoming activity.
await turnContext.SendActivityAsync(MessageFactory.Attachment(heroCard.ToAttachment()), cancellationToken);
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Invoked when a Team Unarchived event activity is received from the connector. Team.
this.onTeamsTeamUnarchivedEvent(async (teamInfo: TeamInfo, turnContext: TurnContext, next: () => Promise<void>): Promise<void> => {
const card = CardFactory.heroCard('Team archived', `${teamInfo.name} is the team name`);
const message = MessageFactory.attachment(card);
// Sends an activity to the sender of the incoming activity.
await turnContext.sendActivity(message);
await next();
});
}
}
# Invoked when a Team Unarchived event activity is received from the connector. Team Unarchived correspond to the user unarchiving a team.
async def on_teams_team_unarchived(
self, team_info: TeamInfo, turn_context: TurnContext
):
# Sends an activity to the sender of the incoming activity.
return await turn_context.send_activity(
MessageFactory.text(f"The team name is {team_info.name}")
)
Now that you've worked with the conversation update events, you can understand the message reaction events that occur for different reactions to a message.
Message reaction events
The messageReaction event is sent when a user adds or removes reactions to a message, which was sent by your bot. The replyToId contains the ID of the message, and the Type is the type of reaction in text format. The types of reactions include angry, heart, laugh, like, sad, and surprised. This event doesn't contain the contents of the original message. If processing reactions to your messages is important for your bot, you must store the messages when you send them. The following table provides more information about the event type and payload objects:
protected override async Task OnReactionsAddedAsync(IList<MessageReaction> messageReactions, ITurnContext<IMessageReactionActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var reaction in messageReactions)
{
var newReaction = $"You reacted with '{reaction.Type}' to the following message: '{turnContext.Activity.ReplyToId}'";
var replyActivity = MessageFactory.Text(newReaction);
// Sends an activity to the sender of the incoming activity.
var resourceResponse = await turnContext.SendActivityAsync(replyActivity, cancellationToken);
}
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Override this in a derived class to provide logic for when reactions to a previous activity.
this.onReactionsAdded(async (context, next) => {
const reactionsAdded = context.activity.reactionsAdded;
if (reactionsAdded && reactionsAdded.length > 0) {
for (let i = 0; i < reactionsAdded.length; i++) {
const reaction = reactionsAdded[i];
const newReaction = `You reacted with '${reaction.type}' to the following message: '${context.activity.replyToId}'`;
// Sends an activity to the sender of the incoming activity.
const resourceResponse = context.sendActivity(newReaction);
// Save information about the sent message and its ID (resourceResponse.id).
}
}
});
}
}
# Override this in a derived class to provide logic for when reactions to a previous activity are added to the conversation.
async def on_reactions_added(
self, message_reactions: List[MessageReaction], turn_context: TurnContext
):
for reaction in message_reactions:
activity = await self._log.find(turn_context.activity.reply_to_id)
if not activity:
# Sends an activity to the sender of the incoming activity.
await self._send_message_and_log_activity_id(
turn_context,
f"Activity {turn_context.activity.reply_to_id} not found in log",
)
else:
# Sends an activity to the sender of the incoming activity.
await self._send_message_and_log_activity_id(
turn_context,
f"You added '{reaction.type}' regarding '{activity.text}'",
)
return
Reactions removed from bot message
The following code shows an example of reactions removed from bot message:
protected override async Task OnReactionsRemovedAsync(IList<MessageReaction> messageReactions, ITurnContext<IMessageReactionActivity> turnContext, CancellationToken cancellationToken)
{
foreach (var reaction in messageReactions)
{
var newReaction = $"You removed the reaction '{reaction.Type}' from the following message: '{turnContext.Activity.ReplyToId}'";
var replyActivity = MessageFactory.Text(newReaction);
// Sends an activity to the sender of the incoming activity.
var resourceResponse = await turnContext.SendActivityAsync(replyActivity, cancellationToken);
}
}
export class MyBot extends TeamsActivityHandler {
constructor() {
super();
// Override this in a derived class to provide logic for when reactions to a previous activity.
this.onReactionsRemoved(async(context,next)=>{
const reactionsRemoved = context.activity.reactionsRemoved;
if (reactionsRemoved && reactionsRemoved.length > 0) {
for (let i = 0; i < reactionsRemoved.length; i++) {
const reaction = reactionsRemoved[i];
const newReaction = `You removed the reaction '${reaction.type}' from the message: '${context.activity.replyToId}'`;
// Sends an activity to the sender of the incoming activity.
const resourceResponse = context.sendActivity(newReaction);
// Save information about the sent message and its ID (resourceResponse.id).
}
}
});
}
}
# Override this in a derived class to provide logic specific to removed activities.
async def on_reactions_removed(
self, message_reactions: List[MessageReaction], turn_context: TurnContext
):
for reaction in message_reactions:
activity = await self._log.find(turn_context.activity.reply_to_id)
if not activity:
# Sends an activity to the sender of the incoming activity.
await self._send_message_and_log_activity_id(
turn_context,
f"Activity {turn_context.activity.reply_to_id} not found in log",
)
else:
# Sends an activity to the sender of the incoming activity.
await self._send_message_and_log_activity_id(
turn_context,
f"You removed '{reaction.type}' regarding '{activity.text}'",
)
return
Installation update event
The bot receives an installationUpdate event when you install a bot to a conversation thread. Uninstallation of the bot from the thread also triggers the event. On installing a bot, the action field in the event is set to add, and when the bot is uninstalled the action field is set to remove.
Note
When you upgrade an application, the bot receives the installationUpdate event only to add or remove a bot from the manifest. For all other cases, the installationUpdate event isn't triggered. The action field is set to add-upgrade if you add a bot or remove-upgrade if you remove a bot.
Install update event
Use the installationUpdate event to send an introductory message from your bot on installation. This event helps you to meet your privacy and data retention requirements. You can also clean up and delete user or thread data when the bot is uninstalled.
Similar to the conversationUpdate event that's sent when bot is added to a team, the conversation.id of the installationUpdate event is set to the id of the channel selected by a user during app installation or the channel where the installation occurred. The id represents the channel where the user intends for the bot to operate and must be used by the bot when sending a welcome message. For scenarios where the ID of the General channel is explicitly required, you can get it from team.id in channelData.
In this example, the conversation.id of the conversationUpdate and installationUpdate activities is set to the ID of the Response channel in the Daves Demo team.
Note
The selected channel id is only set on installationUpdateadd events that are sent when an app is installed into a team.
async onInstallationUpdateActivity(context: TurnContext) {
var activity = context.activity.action;
if(activity == "Add") {
// Sends an activity to the sender of the incoming activity to add.
await context.sendActivity(MessageFactory.text("Added"));
}
else {
// Sends an activity to the sender of the incoming activity to uninstalled.
await context.sendActivity(MessageFactory.text("Uninstalled"));
}
}
# Override this in a derived class to provide logic specific to InstallationUpdate activities.
async def on_installation_update(self, turn_context: TurnContext):
if turn_context.activity.action == "add":
# Sends an activity to the sender of the incoming activity to add.
await turn_context.send_activity(MessageFactory.text("Added"))
else:
# Sends an activity to the sender of the incoming activity to uninstalled.
await turn_context.send_activity(MessageFactory.text("Uninstalled"))
Uninstall behavior for personal app with bot
When you uninstall an app, the bot is also uninstalled. When a user sends a message to your app, they receive a 403 response code. Your bot receives a 403 response code for new messages posted by your bot. The post uninstall behavior for bots in the personal scope with the Teams and groupChat scopes are now aligned. You can't send or receive messages after an app has been uninstalled.
Event handling for install and uninstall events
When you use these install and uninstall events, there are some instances where bots give exceptions on receiving unexpected events from Teams, which occurs in the following cases:
You build your bot without the Microsoft Bot Framework SDK, and as a result the bot gives an exception on receiving an unexpected event.
You build your bot with the Microsoft Bot Framework SDK, and you select to alter the default event behavior by overriding the base event handle.
It's important to know that new events can be added anytime in the future and your bot begins to receive them. So you must design for the possibility of receiving unexpected events. If you're using the Bot Framework SDK, your bot automatically responds with a 200 – OK to any events you don't choose to handle.
Handling errors in conversation events
When a bot encounters an error while handling different events or activities, don't send messages that have no meaningful context to the conversation as shown in the following screenshot:
In the development phase, it's always helpful to send meaningful messages in conversations, which provide additional details about a specific error for better debugging. However, in the production environment, you must log the errors or events to Azure Application Insights. For more information, see Add telemetry to your bot.
Code sample
Sample Name
Description
.NET
Node.js
Python
Manifest
Conversation bot
This sample shows how to use different bot conversation events available in Bot Framework v4 for personal and teams scope.