{"record":{"id":"5a41e0067102844c","repo":"dotnet/aspnetcore","slug":"detected-a-connection-attempt-to-an-asp-net-signal","errorCode":null,"errorMessage":"Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.","messagePattern":"Detected a connection attempt to an ASP\\.NET SignalR Server\\. This client only supports connecting to an ASP\\.NET Core SignalR Server\\. See https://aka\\.ms/signalr-core-differences for details\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/SignalR/clients/ts/signalr/src/HttpConnection.ts","lineNumber":280,"sourceCode":"                    throw new Error(\"Negotiation can only be skipped when using the WebSocket transport directly.\");\n                }\n            } else {\n                let negotiateResponse: INegotiateResponse | null = null;\n                let redirects = 0;\n\n                do {\n                    negotiateResponse = await this._getNegotiationResponse(url);\n                    // the user tries to stop the connection when it is being started\n                    if (this._connectionState === ConnectionState.Disconnecting || this._connectionState === ConnectionState.Disconnected) {\n                        throw new AbortError(\"The connection was stopped during negotiation.\");\n                    }\n\n                    if (negotiateResponse.error) {\n                        throw new Error(negotiateResponse.error);\n                    }\n\n                    if ((negotiateResponse as any).ProtocolVersion) {\n                        throw new Error(\"Detected a connection attempt to an ASP.NET SignalR Server. This client only supports connecting to an ASP.NET Core SignalR Server. See https://aka.ms/signalr-core-differences for details.\");\n                    }\n\n                    if (negotiateResponse.url) {\n                        url = negotiateResponse.url;\n                    }\n\n                    if (negotiateResponse.accessToken) {\n                        // Replace the current access token factory with one that uses\n                        // the returned access token\n                        this._setTransportAccessToken(negotiateResponse.accessToken);\n                    }\n\n                    redirects++;\n                }\n                while (negotiateResponse.url && redirects < MAX_REDIRECTS);\n\n                if (redirects === MAX_REDIRECTS && negotiateResponse.url) {\n                    throw new Error(\"Negotiate redirection limit exceeded.\");","sourceCodeStart":262,"sourceCodeEnd":298,"githubUrl":"https://github.com/dotnet/aspnetcore/blob/3600ca084e9c8b5f4174fc5e747f4c52d2100806/src/SignalR/clients/ts/signalr/src/HttpConnection.ts#L262-L298","documentation":"If the negotiate response contains a ProtocolVersion field, the client infers it is talking to a legacy ASP.NET SignalR server (which uses ProtocolVersion), not ASP.NET Core SignalR. The two protocols are incompatible, so the client throws a descriptive error pointing at the migration docs.","triggerScenarios":"The client's /negotiate response includes a ProtocolVersion property — the hallmark of the older ASP.NET (System.Web / OWIN) SignalR server. Reaches this throw immediately after the negotiate JSON is parsed.","commonSituations":"Migrating an app: the new Core client is pointed at an old ASP.NET hub still running on the same domain. Reverse proxy routes /hubs/* to a legacy server. Developer confused about which SignalR generation their server runs.","solutions":["Point the Core client at an ASP.NET Core SignalR hub (Microsoft.AspNetCore.SignalR), not a legacy ASP.NET hub.","If you must talk to a legacy hub, use the legacy microsoft.aspnet.signalr.client JS package instead of @microsoft/signalr.","Verify the route: ASP.NET Core hubs negotiate at /<hub>/negotiate and return connectionId, not ProtocolVersion.","Update the reverse-proxy routing so Core traffic goes to the Core server."],"exampleFix":"// before: client is @microsoft/signalr pointed at a legacy hub\nconst conn = new signalR.HubConnectionBuilder()\n  .withUrl(\"https://old-site/signalr\") // legacy ASP.NET hub\n  .build();\n\n// after: use the legacy client for a legacy hub\n// npm install microsoft.aspnet.signalr.client\n// or migrate the server to ASP.NET Core SignalR and keep @microsoft/signalr","handlingStrategy":"validation","validationCode":"async function isCoreSignalR(url: string): Promise<boolean> {\n  const r = await fetch(`${url}/negotiate?negotiateVersion=1`, { method: \"POST\" });\n  const body = await r.json().catch(() => ({}));\n  return !(\"ProtocolVersion\" in body);\n}","typeGuard":"function isLegacyNegotiate(v: unknown): boolean {\n  return typeof v === \"object\" && v !== null && \"ProtocolVersion\" in v;\n}","tryCatchPattern":"try { await connection.start(); }\ncatch (e) {\n  if (e instanceof Error && /ASP.NET SignalR Server/.test(e.message)) {\n    console.error(\"Target is legacy ASP.NET SignalR — use the legacy client or migrate the server.\");\n  }\n  throw e;\n}","preventionTips":["Confirm the server is ASP.NET Core SignalR before wiring the Core client.","For legacy hubs, install microsoft.aspnet.signalr.client instead.","Audit reverse-proxy routing during migration so legacy/Core traffic is split correctly."],"tags":["negotiation","version-mismatch","aspnet","migration"],"backgroundTag":null,"analyzedSha":"3600ca084e9c8b5f4174fc5e747f4c52d2100806","analyzedAt":"2026-08-11T16:32:30.678Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}