dotnet/aspnetcore · error · HttpRequestException
Unexpected status code returned from negotiate
Error message
Unexpected status code returned from negotiate: %d %s.
What it means
Thrown inside handleNegotiate when the POST to the /negotiate endpoint returns an HTTP status other than 200. Wrapped as HttpRequestException carrying the status code and status text. The negotiate request is the first thing the client sends during start(); a non-200 here means the server rejected the negotiate handshake before any transport was chosen.
Solutions
- Read the status code and text from the exception: 401/403 -> fix the access token provider; 404 -> correct the hub URL/path; 5xx -> server-side investigation.
- Confirm the URL ends at the hub endpoint and that the server maps that route to a hub.
- Verify the access token provider returns a valid token before each start.
- Check the server logs for the matching negotiate request; ensure the reverse proxy forwards POST /negotiate.
Example fix
// before
HubConnection conn = HubConnectionBuilder.create("http://srv/api") // wrong path -> 404
.build();
// after
HubConnection conn = HubConnectionBuilder.create("http://srv/hubs/chat") // correct hub route
.withAccessTokenProvider(Single.just(token))
.build(); Defensive patterns
Strategy: try-catch
Validate before calling
// Validate reachability and auth before start:
// 1) GET {hub} returns 200/401 (not 404) - confirms the route exists.
// 2) Access token provider returns a non-empty token when auth is required.
String url = "http://srv/hubs/chat";
Type guard
boolean isLikelyHubEndpoint(int statusCode) {
return statusCode == 200 || statusCode == 401 || statusCode == 403;
} Try / catch
try {
conn.start().blockingAwait();
} catch (HttpRequestException e) {
int code = e.getStatusCode(); // or read from the message
// 401/403 -> fix token provider; 404 -> fix URL; 5xx -> server logs
} Prevention
- Confirm the hub URL maps to a real hub route.
- Ensure the access token provider returns a valid token before start().
- Smoke-test POST {hub}/negotiate with curl and the same headers.
When it happens
Trigger: httpClient.post(resolveNegotiateUrl(url)) returns a response whose getStatusCode() != 200 - e.g. 401/403 (auth), 404 (wrong endpoint/path), 500 (server crash), 502/503 (gateway), 426 (protocol upgrade required).
Common situations: Hub URL is wrong (points to a non-hub path, 404); auth token missing/expired (401/403); server/App Service down or misconfigured (500/502/503); CORS/hosting issue blocks negotiate; the endpoint requires a specific negotiate version and rejects v1; reverse proxy not forwarding the negotiate POST.
Related errors
- Error reading NegotiateResponse
- Negotiate redirection limit exceeded.
- ${negotiateResponse.getError()}
- There were no compatible transports on the server.
- MessagePack message was length
AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11).
Data as JSON: /api/errors/748b741377db8773.
Report an issue: GitHub.
Appendix: source
Thrown at src/SignalR/clients/java/signalr/core/src/main/java/com/microsoft/signalr/HubConnection.java:177
this.handshakeResponseTimeout = handshakeResponseTimeout;
}
this.headers = headers;
this.skipNegotiate = skipNegotiate;
this.serverTimeout = serverTimeout;
this.keepAliveInterval = keepAliveInterval;
this.callback = (payload) -> ReceiveLoop(payload);
}
private Single<NegotiateResponse> handleNegotiate(String url, Map<String, String> localHeaders) {
HttpRequest request = new HttpRequest();
request.addHeaders(localHeaders);
return httpClient.post(Negotiate.resolveNegotiateUrl(url, this.negotiateVersion), request).map((response) -> {
if (response.getStatusCode() != 200) {
throw new HttpRequestException(String.format("Unexpected status code returned from negotiate: %d %s.",
response.getStatusCode(), response.getStatusText()), response.getStatusCode());
}
JsonReader reader = new JsonReader(new StringReader(new String(response.getContent().array(), StandardCharsets.UTF_8)));
NegotiateResponse negotiateResponse = new NegotiateResponse(reader);
if (negotiateResponse.getError() != null) {
throw new RuntimeException(negotiateResponse.getError());
}
if (negotiateResponse.getAccessToken() != null) {
localHeaders.put("Authorization", "Bearer " + negotiateResponse.getAccessToken());
}
return negotiateResponse;
});
}
/**View on GitHub (pinned to 3600ca084e)