Microsoft Teams Error: 'BadArgument' — Cause, Fix, and Troubleshooting Guide
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
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, orDELETEto.../conversations/{conversationId}/activitiesreturns400withcode: "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
conversationIdwith the wrongserviceUrl— for example a commercial-cloudserviceUrlwith a GCC/GCC-High id, or one tenant’s id sent to another’s endpoint. - Stale stored references. A
conversationIdcached 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
serviceUrlfor 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
conversationIdis only valid against theserviceUrlit 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
serviceUrlwill 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 Foundmeans the target no longer exists;BadArgumentmeans the reference you supplied is itself invalid.
Related tenant errors
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?
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.