Skip to main content
StandIn is bring-your-own-bot: your tenant owns the Azure AD app identity, and you publish a small Teams app package that points Teams at it. This page walks the Teams Developer Portal flow using our example manifest. It’s the same for the OpenClaw and Hermes plugins.
The example below contains no secrets - only placeholder GUIDs and product copy. Replace the placeholders with your own values before you build and upload the package.

Download the example package

standin-teams-manifest.zip - manifest.json + placeholder icons + a README. Replace the placeholder GUIDs and icons, then import it in the Developer Portal or upload it to Teams.

Steps

1

Create an Azure Bot (Entra app)

In the Azure portal, create an Azure Bot resource. Note its Microsoft Entra application (client) ID - this is your botId. You’ll set its calling webhook + messaging endpoint to the URLs from your StandIn dashboard.
2

Open the Teams Developer Portal

Go to dev.teams.microsoft.comApps. Either Import an app (upload a zip of the manifest below) or New app and paste the fields in the GUI. The portal validates the manifest and manages versions for you.
3

Fill in the manifest

Use the example manifest and replace the placeholders - chiefly the app id, your botId, and validDomains (your StandIn host).
4

Add icons

Provide a color icon (192×192 px) and an outline icon (32×32 px, transparent). In the portal these upload under Branding; in a manual zip they’re color.png and outline.png.
5

Configure the bot endpoints

On the Azure Bot (same Entra app as botId): set the Teams channel calling webhook and the messaging endpoint to the URLs shown in your StandIn dashboard, and admin-consent the Graph permissions.
6

Publish or download

In the Developer Portal, Publish → Publish to your org (admin approves in Teams Admin Center), or Download the app package and sideload it for testing.
7

Register your plugin in StandIn

Finally, in your StandIn dashboard register your plugin’s WebSocket URL and the matching HMAC sharedSecret. Then place a Teams call to your bot.

Placeholders

The example uses obvious placeholders - do not publish with these values.
FieldExample valueReplace with
id00000000-…-000000000000A new random GUID for the app package (unique per listing; not the bot ID).
bots[].botId11111111-…-111111111111Your Entra application (client) ID (same as the Azure Bot’s MicrosoftAppId).
webApplicationInfo.idsame as botIdMust equal botId for a standard single-app bot.
validDomains[]Your StandIn host, e.g. ["standin.komaa.com"].
Generate GUIDs with uuidgen (macOS/Linux), [guid]::NewGuid() (PowerShell), or uuidgenerator.net.

Example manifest

manifest.json
{
  "$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": "StandIn", "full": "StandIn - Teams Voice & Video AI" },
  "developer": {
    "name": "Komaa",
    "websiteUrl": "https://standin.komaa.com",
    "privacyUrl": "https://komaa.com/privacy",
    "termsOfUseUrl": "https://komaa.com/terms"
  },
  "description": {
    "short": "Voice & video AI for Teams - converse, see the caller, appear as an avatar.",
    "full": "StandIn turns your Microsoft Teams bot into a Conversational Video Interface (CVI): callers talk to your AI in real time, share camera and screen, and see a lip-synced avatar on the bot tile. Subscribe at standin.komaa.com, connect your own Azure bot, and pair it with the Teams Voice Plugin for OpenClaw or Hermes Agent."
  },
  "icons": { "outline": "outline.png", "color": "color.png" },
  "accentColor": "#16A34A",
  "bots": [
    {
      "botId": "11111111-1111-1111-1111-111111111111",
      "scopes": ["personal", "team", "groupChat"],
      "isNotificationOnly": false,
      "supportsCalling": true,
      "supportsVideo": true,
      "supportsFiles": true
    }
  ],
  "validDomains": [],
  "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" }
      ]
    }
  }
}
Key bot fields: supportsCalling: true (required for voice) and supportsVideo: true (required for the CVI avatar tile). The resourceSpecific block is per-team consent (RSC) - granted when the app is installed in a team; it does not replace the tenant-wide Graph permissions below.

Graph permissions

Set these as application permissions on the same Entra app as botId, then admin-consent them in your tenant (this matches the OpenClaw and Hermes configuration pages):
PermissionPurpose
Calls.JoinGroupCall.Allanswer / join Teams calls and meetings
Calls.AccessMedia.Allreal-time call audio/video media
Calls.InitiateGroupCall.Alloutbound “call me back” (skip if unused)
Chat.Read.Allchat / thread context
ChatMessage.Read.Chatread messages in chats the bot is in
Sites.ReadWrite.AllSharePoint / OneDrive file cards for meeting minutes (optional)

Packaging it manually

If you build the zip yourself instead of using the Developer Portal, put the three files at the root of the archive (Teams rejects a package where they sit in a subfolder):
msteams.zip
├── manifest.json
├── color.png
└── outline.png
Zip the files themselves, not the containing folder - msteams/manifest.json inside the zip will be rejected. Bump version whenever you republish the package.