dotnet/aspnetcore · critical · Error

Detected a connection attempt to an ASP.NET SignalR Server.

Error message

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.

What it means

Thrown at HttpConnection.ts:280 when the negotiate response payload contains a `ProtocolVersion` field. ASP.NET (non-Core) SignalR's negotiate response includes ProtocolVersion, whereas ASP.NET Core SignalR does not. This client (the ASP.NET Core JS client) is protocol-incompatible with the older server, so it refuses to continue.

Source

Thrown at src/SignalR/clients/ts/signalr/src/HttpConnection.ts:280

                    throw new Error("Negotiation can only be skipped when using the WebSocket transport directly.");
                }
            } else {
                let negotiateResponse: INegotiateResponse | null = null;
                let redirects = 0;

                do {
                    negotiateResponse = await this._getNegotiationResponse(url);
                    // the user tries to stop the connection when it is being started
                    if (this._connectionState === ConnectionState.Disconnecting || this._connectionState === ConnectionState.Disconnected) {
                        throw new AbortError("The connection was stopped during negotiation.");
                    }

                    if (negotiateResponse.error) {
                        throw new Error(negotiateResponse.error);
                    }

                    if ((negotiateResponse as any).ProtocolVersion) {
                        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.");
                    }

                    if (negotiateResponse.url) {
                        url = negotiateResponse.url;
                    }

                    if (negotiateResponse.accessToken) {
                        // Replace the current access token factory with one that uses
                        // the returned access token
                        this._setTransportAccessToken(negotiateResponse.accessToken);
                    }

                    redirects++;
                }
                while (negotiateResponse.url && redirects < MAX_REDIRECTS);

                if (redirects === MAX_REDIRECTS && negotiateResponse.url) {
                    throw new Error("Negotiate redirection limit exceeded.");

View on GitHub (pinned to 294cab2f9b)

Solutions

  1. Point the client at an ASP.NET Core SignalR hub endpoint (MapHub in Startup.cs/Program.cs), not a legacy ~/signalr endpoint.
  2. If you must talk to a legacy ASP.NET server, use the legacy `jquery.signalR-x.x.x.js` client instead.
  3. Verify the negotiate URL returns Core-style JSON (no ProtocolVersion field).
  4. Check your reverse proxy / load balancer routes to the Core app pool.

Example fix

// before (wrong: legacy endpoint)
new HubConnectionBuilder().withUrl('https://app.example.com/signalr').build();

// after (ASP.NET Core hub)
new HubConnectionBuilder().withUrl('https://app.example.com/hubs/chat').build();
Defensive patterns

Strategy: validation

Validate before calling

async function isAspNetCoreHub(url) {
  const res = await fetch(url.replace(/\/$/, '') + '/negotiate', { method: 'POST' });
  const json = await res.json();
  return !('ProtocolVersion' in json);
}
// if (!await isAspNetCoreHub(url)) throw new Error('not a Core hub');

Type guard

function isCoreNegotiateResponse(r: any): boolean {
  return r && !('ProtocolVersion' in r);
}

Try / catch

try { await hub.start(); }
catch (e) {
  if (/ASP.NET SignalR Server/.test(String(e))) {
    throw new Error('Wrong client/server combo: server is legacy ASP.NET SignalR, use jquery.signalR or fix endpoint');
  }
  throw e;
}

Prevention

When it happens

Trigger: Pointing the @microsoft/signalr client at a server running the legacy ASP.NET SignalR stack (System.Web or Microsoft.AspNet.SignalR) — e.g. an endpoint like `/signalr/negotiate` instead of `/hub/negotiate`. The legacy server returns JSON with a ProtocolVersion key and the guard trips.

Common situations: Migrating from ASP.NET to ASP.NET Core but the old endpoint is still deployed; mixing up `/signalr/hubs` (legacy) vs `/hubs/chat` (Core); reading an old tutorial that uses the `jquery.signalR.js` client against a Core server or vice versa; pointing at a misconfigured reverse proxy that routes to the legacy app.

Related errors


AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06). Data as JSON: /api/errors/5a41e0067102844c. Report an issue: GitHub.