Microsoft Teams Integration — Reference

Azure App Registration (Entra ID)

Field Value
Display name Orcha
Application (client) ID eda62fe4-7d17-4f58-af23-838e1b554458
Object ID a97616b0-c7e6-4a69-aea1-e7f7ec21c452
Directory (tenant) ID 818432d6-b012-47e8-a326-09e010bf22d0
Supported account types Multiple Entra ID tenants (all tenants)
Client credentials 1 secret (no certificates)
State Activated

Portal URL: https://portal.azure.com → Entra ID → App registrations → "Orcha"

Authentication

Setting Value
Platform Web
Redirect URI https://orcha.ngrok.app/settings/notifications/teams/callback

For production, add: https://app.getorcha.com/settings/notifications/teams/callback

API Permissions (Microsoft Graph)

Permission Type Admin Consent Purpose
Channel.ReadBasic.All Application Yes (granted) List channels in a team via Graph API
Team.ReadBasic.All Application Yes (granted) List teams (not currently used but useful)
User.Read Delegated No Default permission, not actively used

Note: Message sending uses Bot Framework API (https://api.botframework.com), not Graph API, so no Graph permission is needed for sending notifications.

Azure Bot Resource

Important: Separate from the App Registration. This is where the messaging endpoint is set.

Setting Value
Messaging endpoint https://orcha.ngrok.app/webhooks/teams (local dev)
Messaging endpoint https://app.getorcha.com/webhooks/teams (production)

Portal URL: https://portal.azure.com → search "Azure Bot" or "Bot Services"

When switching between local dev and production, update the messaging endpoint accordingly.

SSM Parameters (AWS Parameter Store)

Parameter Purpose
/v1-orcha/teams-bot-app-id Same as Application (client) ID above
/v1-orcha/teams-bot-app-secret Client secret from Certificates & secrets
/v1-orcha/teams-state-secret HMAC key for signing OAuth state params

Local dev values are in scripts/init_aws.clj (lines 71-77). Test values are in test/com/getorcha/test/fixtures.clj.

Config (config.edn)

:teams {:bot-app-id     #orcha/param "/v1-orcha/teams-bot-app-id"
        :bot-app-secret #orcha/param "/v1-orcha/teams-bot-app-secret"
        :bot-tenant-id  "818432d6-b012-47e8-a326-09e010bf22d0"  ;; hardcoded Orcha tenant
        :state-secret   #orcha/param "/v1-orcha/teams-state-secret"}

bot-tenant-id is hardcoded because Bot Framework tokens are always obtained from the app's own tenant, regardless of which customer tenant the bot is installed in.

URLs & Endpoints

Local dev base URL

https://orcha.ngrok.app (configured in config.edn line 20, profile :local-dev)

Production base URL

https://app.getorcha.com

App Routes

Route Method Purpose
/notifications GET Notifications page (Teams UI lives here)
/settings/notifications/teams/authorize GET Initiates admin consent redirect
/settings/notifications/teams/callback GET Microsoft redirects back here after consent
/settings/notifications/teams/{id}/channels GET HTMX: fetch available channels dropdown
/settings/notifications/teams/{id}/update-channel POST Save selected channel
/settings/notifications/teams/{id}/disconnect POST Remove Teams connection
/webhooks/teams POST Bot Framework sends events here

Microsoft URLs used by the code

URL Purpose
https://login.microsoftonline.com/common/adminconsent Admin consent flow start
https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token Graph API token (per customer tenant)
https://login.microsoftonline.com/{bot-tenant-id}/oauth2/v2.0/token Bot Framework token (Orcha tenant)
https://graph.microsoft.com/v1.0/teams/{team-id}/channels List channels in a team
{service-url}/v3/conversations/{channel-id}/activities Send message via Bot Framework
https://login.botframework.com/v1/.well-known/openidconfiguration JWT verification keys for webhook

Teams App Manifest

Location: resources/teams-app/manifest.json

Field Value
App ID eda62fe4-7d17-4f58-af23-838e1b554458
Name Orcha / Orcha Notifications
Bot scopes ["team"]
isNotificationOnly true
Valid domains orcha.ngrok.app, app.getorcha.com

To sideload: zip manifest.json + color.png (192x192) + outline.png (32x32) into a .zip.

Database Schema

notification_channel (base table)

Column Type Notes
id UUID (PK)
tenant_id UUID (FK)
channel_type enum: email, slack, teams
status enum: pending, active, disabled

notification_channel_teams

Column Type Notes
channel_id UUID (FK → notification_channel.id)
tenant_id UUID Microsoft tenant ID of the customer
team_id TEXT Thread-format ID (e.g. 19:...@thread.tacv2)
team_aad_group_id TEXT Azure AD group ID (UUID format, for Graph API)
team_name TEXT Display name of the team
teams_channel_id TEXT Channel thread ID
teams_channel_name TEXT Display name of the channel
service_url TEXT Bot Framework service URL
conversation_id TEXT Bot Framework conversation ID

Flow Summary

1. User clicks "Add to Microsoft Teams" on /notifications
   → GET /settings/notifications/teams/authorize
   → 302 to https://login.microsoftonline.com/common/adminconsent?...

2. Customer admin grants consent on Microsoft's page
   → Microsoft redirects to /settings/notifications/teams/callback
   → Creates notification_channel (status=pending) + notification_channel_teams (tenant_id only)

3. Customer installs bot in their Teams team (sideload or app store)
   → Microsoft sends POST /webhooks/teams (conversationUpdate)
   → Updates notification_channel_teams with team_id, team_name, service_url, conversation_id

4. User selects a channel on /notifications page
   → GET /settings/notifications/teams/{id}/channels (fetches from Graph API)
   → POST /settings/notifications/teams/{id}/update-channel
   → Updates teams_channel_id, teams_channel_name, sets status=active

5. Notification triggered
   → Bot Framework token obtained from Orcha's tenant
   → Adaptive Card posted to {service_url}/v3/conversations/{channel_id}/activities
   → Message appears in Teams channel

Migrations

File Description
20260218120000-add-teams-notification-channel.up.sql Creates notification_channel_teams table
20260222123700-add-teams-aad-group-id.up.sql Adds team_aad_group_id column
20260222123700-add-teams-aad-group-id.down.sql Drops team_aad_group_id column