Skip to content

[Bug]: Teams webhook broken in 2026.3.24+: publicUrl removed breaks JWT validation #58249

@Thrinariax3

Description

@Thrinariax3

Bug type

Regression (worked before, now fails)

Beta release blocker

No

Summary

Description

After the Teams SDK migration in PR #51808 (2026.3.24+), Microsoft Teams webhook messages no longer arrive at OpenClaw. The webhook endpoint responds with 401 Unauthorized to all inbound requests.

Root Cause

In version 2026.3.23 and earlier, OpenClaw's Teams configuration included a gateway.publicUrl field:

{
"gateway": {
"publicUrl": "[tailscale-domain]"
}
}
This field was removed in 2026.3.24+ when the Teams extension was migrated to the official Microsoft Teams SDK (PR #51808). However, the Teams SDK requires knowledge of the public webhook URL for proper JWT token validation:

  1. Microsoft Bot Framework sends a JWT token signed with the bot's credentials
  2. The Teams SDK's createServiceTokenValidator() must validate this token
  3. Token validation requires the service URL (the public webhook endpoint) to verify the token claim
  4. Without publicUrl, OpenClaw cannot construct the correct service URL, causing validation to fail with 401 Unauthorized

Impact

• Teams DMs and channel messages do not reach the bot
• The webhook endpoint (/api/messages) accepts requests but rejects them as unauthorized
• Users cannot interact with the OpenClaw bot in Teams
• Downgrade to 2026.3.23 or earlier is required as a workaround

Environment

• OpenClaw version: 2026.3.28 (and 2026.3.24-2026.3.27)
• Teams plugin version: 2026.3.13+
• Configuration: Teams webhook on port 3978, Tailscale routing to /teams/api/messages

Steps to Reproduce

  1. Update OpenClaw to 2026.3.24 or later
  2. Configure Teams bot with endpoint https://[public-url]/teams/api/messages
  3. Send a DM or message to the bot in Teams
  4. Observe: No message received, webhook logs show 401 Unauthorized

Potential Fix

Option A: Restore publicUrl field (Recommended)

Add publicUrl back to the gateway config schema and Teams extension:

// openclaw.json
{
"gateway": {
"port": [PORT],
"publicUrl": "[URL]"
},
"channels": {
"msteams": {
"webhook": { "port": [PORT], "path": "/api/messages" }
}
}
}
Then in the Teams extension, use publicUrl to construct the service URL for JWT validation:

// src/extensions/msteams/bot-adapter.ts
const validator = createServiceTokenValidator({
microsoftAppId: appId,
microsoftAppPassword: appPassword,
// Use gateway.publicUrl + webhook.path as the service URL
serviceUrl: ${config.gateway?.publicUrl}${config.channels?.msteams?.webhook?.path || '/api/messages'}
});
Option B: Auto-detect service URL from request headers

Extract the service URL from incoming webhook requests (Teams includes this in the HTTP headers or JWT claims):

// Fallback: read serviceUrl from the JWT or request payload
const incomingServiceUrl = activity.serviceUrl || request.headers['x-service-url'];
const validator = createServiceTokenValidator({
microsoftAppId: appId,
microsoftAppPassword: appPassword,
serviceUrl: incomingServiceUrl
});
Option C: Make serviceUrl optional in validator

If the Teams SDK's token validator supports optional service URL verification, disable strict URL validation for local/development setups:

const validator = createServiceTokenValidator({
microsoftAppId: appId,
microsoftAppPassword: appPassword,
validateServiceUrl: false // Accept tokens from any service URL
});
(Note: This is less secure and should only be used if the validator library supports it.)

Recommendation

Option A is the cleanest fix: Restore publicUrl as an optional gateway field and pass it to the Teams extension. This maintains backward compatibility and allows Teams webhook routing to work correctly.

If that's not viable, Option B (auto-detect from request) is a good fallback since Microsoft Bot Framework includes the service URL in webhook requests.

───
Workaround for users: Downgrade to OpenClaw 2026.3.23 or earlier until this is fixed.

Steps to reproduce

Steps to Reproduce

Prerequisites

• OpenClaw 2026.3.24 or later (any version affected by the Teams SDK migration)
• Microsoft Teams bot registered in Azure Bot Service
• Bot credentials (App ID and Client Secret)
• Public URL accessible from Microsoft Bot Framework (e.g., via Tailscale, ngrok, or public domain)

Setup

  1. Configure OpenClaw with Teams

Edit openclaw.json

cat > /root/.openclaw/openclaw.json << 'EOF'
{
"channels": {
"msteams": {
"enabled": true,
"appId": "your-app-id-here",
"appPassword": "your-client-secret-here",
"tenantId": "your-tenant-id-here",
"webhook": {
"port": [PORT],
"path": "/api/messages"
},
"dmPolicy": "open",
"groupPolicy": "open"
}
},
"gateway": {
"port": [PORT]
}
}
EOF
2. Set up public routing (via Tailscale or equivalent)

Route HTTPS traffic to the local webhook

tailscale serve --set-path /teams --bg 127.0.0.1:[PORT]
tailscale serve --bg 127.0.0.1:[PORT]

Verify

tailscale serve status

Output should show:

https://[your-tailscale-hostname]/ proxy http://127.0.0.1:[PORT]/

https://[your-tailscale-hostname]/teams proxy http://127.0.0.1:[PORT]/

  1. Configure Teams Bot in Azure Portal

  2. Navigate to Azure Bot Service → Your bot

  3. Go to Configuration → Messaging endpoint

  4. Set the endpoint to: https://[your-tailscale-hostname]/teams/api/messages

  5. Save changes

  6. Start OpenClaw Gateway

openclaw gateway start

or restart if already running

openclaw gateway restart
Reproduce the Issue

  1. Send a Test Message

  2. Open Microsoft Teams

  3. Find your bot in the chat list (or add it to a channel)

  4. Send a direct message to the bot: "Hello"

Expected behavior

Expected Behavior (2026.3.23 and earlier)

• Bot receives the message
• Bot responds with a reply

Actual behavior

Actual Behavior (2026.3.24+)

• Message is sent to Teams
• No response from the bot
• Check OpenClaw logs for 401 Unauthorized errors

OpenClaw version

2026.3.28 (also affects 2026.3.24 - 2026.3.27)

Operating system

Ubuntu 24.04.4 LTS

Install method

npm

Model

| Component | Configuration | | ---------------- | --------------------------------------------------------------------------------------------------------------------------- | | Default Agent | main (claude-haiku-4-5-20251001) | | Available Models |

  • anthropic/claude-sonnet-4-6
  • anthropic/claude-haiku-4-5-20251001
  • anthropic/claude-opus-4-6
| | Cache Retention | short (all models) |

Provider / routing chain

Microsoft Teams Bot Message ↓ HTTPS Request to [tailscale-domain]/teams/api/messages ↓ Tailscale Magic DNS (resolves to IP) ↓ Tailscale Serve Proxy (routes /teams → http://127.0.0.1:[PORT]) ↓ OpenClaw Gateway (port [PORT], Teams extension) ↓ Teams SDK createServiceTokenValidator() ↓ ❌ FAILS: 401 Unauthorized (JWT validation fails - no publicUrl available)

Additional provider/model setup details

No response

Logs, screenshots, and evidence

Impact and severity

Agent does not respond to bot messages after upgrading.

Additional information

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingregressionBehavior that previously worked and now fails

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions