The channels.msteams settings, the environment variables that stand in for the auth keys, and the history context rules.

Environment variables

These auth-related config keys can be set via environment variables instead of apus.json (other config keys, such as groupPolicy or historyLimit, are config-only):

Env var Config key Notes
MSTEAMS_APP_ID appId
MSTEAMS_APP_PASSWORD appPassword
MSTEAMS_TENANT_ID tenantId
MSTEAMS_AUTH_TYPE authType "secret" or "federated"
MSTEAMS_CERTIFICATE_PATH certificatePath federated + certificate
MSTEAMS_CERTIFICATE_THUMBPRINT certificateThumbprint accepted, not required for auth
MSTEAMS_USE_MANAGED_IDENTITY useManagedIdentity federated + managed identity
MSTEAMS_MANAGED_IDENTITY_CLIENT_ID managedIdentityClientId user-assigned managed identity only

History context

Configuration

Key settings (see /gateway/configuration for shared channel patterns):

Migrating an existing webhook endpoint

Teams webhooks now share the Gateway HTTP listener. The Teams SDK still verifies Azure JWT signatures; callers do not supply a Gateway token. Keep the public HTTPS messaging endpoint in Azure Bot and change its reverse-proxy upstream to Gateway port 18789 (or your gateway.port), preserving /api/messages or your configured webhook.path. If you expose a port directly, update Azure Bot's messaging endpoint to the public HTTPS URL that reaches this Gateway route.

Doctor pins legacyWebhook: { port: 3978 } once for an enabled Teams channel on an existing installation that relied on the implicit port. Evidence of prior operation is required; fresh installations open no separate listener. An explicitly configured webhook.port moves to legacyWebhook.port through Doctor's normal config backup and write flow. Pinned compatibility listeners forward into the same Gateway route and JWT validation.

The deprecated TypeScript webhook.port input remains source-compatible until the next Plugin SDK major. Runtime config uses legacyWebhook; run apus doctor --fix to migrate the old key.

After confirming a delivery through the Gateway port, remove the channels.msteams.legacyWebhook pin and any old firewall or Compose port mapping. Keep meta.migrations.webhookListeners, which Doctor saves with the pin, so later runs do not recreate it. See webhook migrations for included and read-only config sources. Explicit false also disables the listener. Doctor and startup print the Gateway route and the setting to remove.

For an existing installation that supplies Teams credentials only through environment variables, Doctor preserves the endpoint without enabling Teams in the source config. If those credentials are visible only to the Gateway service, Doctor leaves the decision pending until Gateway startup. Continue using gateway run --ambient-channels to activate it. A listener setting alone no longer enables Teams; use enabled: true for a permanent opt-in. Doctor adds that flag to authored listener settings that previously implied activation, including an explicit legacyWebhook: false. It does not add the flag to a generated pin whose migration completion is already recorded.

A custom path also accepts the older /api/messages alias with its existing deprecation warning when that route is available. If another plugin owns the alias, startup logs the conflict and keeps serving the configured path. Update Azure Bot to the configured path.

The Gateway reserves /health, /healthz, /ready, /readyz, /startup, and /startupz for checks, including URLs with query strings. If your former Teams callback uses one of these paths, set webhook.path to /api/messages and update Azure Bot or the proxy upstream to match. Doctor reports this conflict. The compatibility listener keeps the old endpoint working; startup refuses the unusable Gateway route when no explicit legacy listener is configured. Verify the replacement before removing that listener's pin.

Paths under /api/channels require Gateway authentication on the main listener, including encoded spellings. Teams callbacks authenticate with Azure JWTs, so use /api/messages instead. Doctor and startup report the same callback-change action; the compatibility listener continues serving the old path until that cutover is complete.

Express parameter, wildcard, and brace patterns continue working on the legacy port. Gateway route registration uses literal paths. Before disabling the legacy listener for a pattern, change the existing webhook.path to /api/messages and update Azure Bot or your proxy. Doctor and startup identify these patterns; without an explicit legacy listener, startup reports the required change instead of silently dropping callbacks.