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
- Point the client at an ASP.NET Core SignalR hub endpoint (MapHub in Startup.cs/Program.cs), not a legacy ~/signalr endpoint.
- If you must talk to a legacy ASP.NET server, use the legacy `jquery.signalR-x.x.x.js` client instead.
- Verify the negotiate URL returns Core-style JSON (no ProtocolVersion field).
- 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
- Confirm the endpoint path is the Core MapHub route, not ~/signalr.
- Match the client package (@microsoft/signalr) to a Core server.
- Check negotiate JSON: Core does not return ProtocolVersion.
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
- Unknown type '${type}' for parameter '${parameterName}'
- Invalid component type '${type}'.
- Unexpected renderer ID '${rendererId}' encountered while dis
- Invalid JS call result type '${resultType}'.
- Negotiation can only be skipped when using the WebSocket tra
AI-assisted analysis of dotnet/aspnetcore@294cab2f9b (2026-08-06).
Data as JSON: /api/errors/5a41e0067102844c.
Report an issue: GitHub.