Microsoft Teams

Embed
Preview of the card
<a href="https://forumulate.com/p/watchtower/docs/teams"><img src="https://forumulate.com/api/public/orgs/watchtower/spaces/docs/pages/1c4f97c9-3831-47b2-bafc-605bc8c042f3/social-card.svg?v=3e7ae33eec8b" alt="Microsoft Teams" width="600" height="315"></a>

The Watchtower Teams app is a bot that posts Adaptive Cards and runs Action.Execute buttons as the linked Watchtower user. One Entra app registration serves every tenant.

Set up (operator)

  1. Register a multi-tenant bot (Azure Bot) and a Teams app package that points at https://<WT_PUBLIC_URL>/connect/teams/messages.

  2. Set on the notifier: WT_PUBLIC_URL, WT_INTERNAL_TOKEN, WT_API_INTERNAL_URL, WT_TEAMS_APP_ID, WT_TEAMS_APP_PASSWORD, WT_SECRET_KEY. Add https://<WT_PUBLIC_URL>/connect/teams/install as a redirect URI of the app registration.

Install (organization admin)

The API gets an install link from POST /internal/connect/teams/state {org_id, user_id} (after it checked ManageConnectors) and sends the admin to it. The page sets a browser-binding cookie and shows a one-time code (10 minutes, stored as a hash). The admin adds the Watchtower app to a team and sends @Watchtower connect <code> in a channel of that team. The tenant id is read only from that signed Bot Framework activity (never from a URL parameter); the code ties it to the org that issued it. That channel becomes the org's conversation. Chats and group chats never connect a tenant and never move the conversation, and a tenant already connected to another organization is refused.

Create a receiver of kind teams_app with public config {"installation_id": "<id>"}.

What it does

An alert notification is an Adaptive Card (version 1.4): per firing alert (up to five) a title, its labels and the buttons Ack, Resolve, Silence 1h and Declare incident, then the resolved alerts. Each button is an Action.Execute with the verb ack, resolve, silence or declare and data {p: project, t: alert}. A press calls POST /internal/v1/actions/{verb} as the linked user. Teams has no idempotency key, so a retry after a lost response can post a card twice.

Accounts are linked with a code: the user sends the bot link, gets a one-time code and enters it in the Watchtower web app while signed in (POST /v1/connectors/link). Teams has no email auto-link. An unlinked user is told to send link and nothing runs.

Security

Every request to /connect/teams/messages carries a Bot Framework JWT. Watchtower fetches the signing keys from the OpenID metadata (https://login.botframework.com/v1/.well-known/openidconfiguration), checks the RS256 signature, the issuer (https://api.botframework.com), the audience (the bot's app id) and the validity window. The activity must have channelId msteams (Web Chat and Direct Line let the client pick tenant and user ids), the token must carry a serviceurl claim equal to the activity's serviceUrl, and its host must be smba.trafficmanager.net, *.botframework.com or *.teams.microsoft.com. Any failure is a bare 401. The bot's token is only sent to such hosts, and all outbound calls use the SSRF-guarded client.

Known limits

The JWKS endorsement check is replaced by the channelId check above. Cards are sent as new messages; they are not updated in place. 429 responses honour Retry-After (at most one hour).

Updated