Skip to content
🎉 Launch sale:50% off everything over $22 — automatically applied at checkout· ends Aug 2Shop the sale →
DevOps AI ToolKit
Newsletter
All guides
AI for Microsoft Teams By James Joyner IV · · 8 min read Last reviewed Jul 2026

Microsoft Teams Error: 'BadArgument' — Cause, Fix, and Troubleshooting Guide

Quick answer

Fix the Bot Framework connector BadArgument 'Invalid conversation id.' error (HTTP 400) caused by malformed, stale, or cross-tenant conversationId values.

  • #microsoft-teams
  • #troubleshooting
  • #errors
  • #bot-framework
Free toolkit

Stuck on this Microsoft Teams error? Get the free incident triage checklist

A one-page PDF — the exact steps to isolate, fix, and verify a production error like this one. No spam, unsubscribe anytime.

What this error means

The Bot Framework connector returns HTTP 400 Bad Request with error code BadArgument when a send, update, or delete activity call references a conversation the connector cannot accept for the given serviceUrl. The most common message is Invalid conversation id.. This happens when the conversationId is malformed, stale, or paired with a serviceUrl from a different tenant or cloud.

The literal response body:

HTTP/1.1 400 Bad Request
Content-Type: application/json

{
  "error": {
    "code": "BadArgument",
    "message": "Invalid conversation id."
  }
}

The failing request is a standard connector call keyed on the conversation:

POST {serviceUrl}v3/conversations/{conversationId}/activities

conversationId and serviceUrl are a matched pair. Teams issues a serviceUrl per channel/cloud, and the conversationId is only valid against the serviceUrl it was minted for. If you assemble a call from mismatched sources — a conversationId from one tenant with a serviceUrl from another, or a hand-built id — the connector rejects it as a bad argument.

What users report

  • POST, PUT, or DELETE to .../conversations/{conversationId}/activities returns 400 with code: "BadArgument".
  • Message text reads Invalid conversation id. (or a closely related argument message).
  • Reactive replies to incoming activities work, but proactive sends from stored ids fail.
  • Ids that worked days ago now fail after an app reinstall or channel change.
  • Failures appear only for certain tenants in a multi-tenant bot.
  • The same conversation reference fails on update/delete even though the original send succeeded.

Tenant and app configuration causes

  • Fabricated or hand-edited conversationId. Constructing the id from team/channel parts instead of using the value Teams supplied yields an id the connector will not accept.
  • Cross-tenant or cross-cloud mismatch. Pairing a conversationId with the wrong serviceUrl — for example a commercial-cloud serviceUrl with a GCC/GCC-High id, or one tenant’s id sent to another’s endpoint.
  • Stale stored references. A conversationId cached long ago becomes invalid after the app is uninstalled/reinstalled, the channel is deleted, or the conversation is otherwise reset.
  • Truncated or mutated id. URL-encoding, trimming, or database round-trips that alter the exact id string (which may contain special characters) break the match.
  • Wrong id field. Using a message id, activity id, or thread id where the connector expects conversation.id.
  • Mixed-up serviceUrl. Hardcoding a single serviceUrl for all conversations rather than storing the one that arrived with each conversation.

Confirming tenant configuration

Log the exact conversation.id and serviceUrl from the incoming activity, and log the exact pair you use on every outbound call. Compare them byte for byte.

Capture on inbound (C#):

logger.LogInformation("Inbound serviceUrl={ServiceUrl} conversationId={ConversationId}",
    turnContext.Activity.ServiceUrl,
    turnContext.Activity.Conversation.Id);

Log what you actually send when doing a proactive message:

logger.LogInformation("Proactive send serviceUrl={ServiceUrl} conversationId={ConversationId}",
    conversationReference.ServiceUrl,
    conversationReference.Conversation.Id);

If you have the raw incoming activity JSON, pull the fields directly:

jq '{serviceUrl: .serviceUrl, conversationId: .conversation.id, tenant: .conversation.tenantId}' activity.json

Confirm the stored pair matches the live one, and that the conversationId has not been altered in transit:

diff <(echo "$STORED_CONVERSATION_ID") <(echo "$LIVE_CONVERSATION_ID")

A non-empty diff, a serviceUrl host that differs from where the id came from, or a mismatched tenantId all confirm the cause.

Resolution

Never fabricate a conversationId. Capture it from a real incoming activity and persist it together with the serviceUrl (and tenant) it arrived with. When you send proactively, rebuild the conversation reference from the stored pair — do not mix a stored id with a default or shared serviceUrl.

In the Bot Framework SDK, save the reference from Activity.GetConversationReference() and reuse the whole object:

var reference = turnContext.Activity.GetConversationReference();
await store.SaveAsync(reference); // persists ServiceUrl + Conversation.Id + tenant together

// later, proactive send
await adapter.ContinueConversationAsync(appId, reference, BotCallback, cancellationToken);

Trust the connector’s serviceUrl for each conversation rather than hardcoding one. Before trusting it for out-of-turn sends, call MicrosoftAppCredentials.TrustServiceUrl(serviceUrl) (or the adapter equivalent) using the exact value that arrived.

Refresh stale references. If a proactive send returns BadArgument, treat the stored reference as dead: stop retrying it, drop it, and re-acquire a fresh reference the next time the user or channel interacts with the bot. For multi-tenant bots, key stored references by tenant so you never cross a serviceUrl and conversationId between tenants.

Avoiding tenant drift

  • The pair is inseparable. A conversationId is only valid against the serviceUrl it was issued for; store and use them together.
  • Ids can contain special characters. Preserve the exact string — do not URL-encode, trim, or re-case it when persisting or sending.
  • Reinstalls invalidate ids. Uninstalling and reinstalling the app, or deleting a channel, can void previously valid conversation references.
  • Clouds differ. GCC, GCC-High, and DoD use different endpoints; a commercial serviceUrl will never accept a sovereign-cloud id.
  • BadArgument is not throttling. It will not resolve with retries or backoff; the argument itself must change.
  • Distinguish from 404. A 404 Resource Not Found means the target no longer exists; BadArgument means the reference you supplied is itself invalid.
Free download · 368-page PDF

Fixed it? Get 500 Microsoft Teams & DevOps AI prompts — free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.

Did this fix your issue?

Free download · 368-page PDF

Get 500 Battle-Tested DevOps AI Prompts — Free

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.