dotnet/aspnetcore · error · Error

Expected a handshake response from the server.

Error message

Expected a handshake response from the server.

What it means

After parsing the handshake frame, HandshakeProtocol checks whether the decoded JSON has a top-level 'type' field. A genuine handshake response has no type (it carries error/minorVersion/userId), so presence of type means the server sent a regular HubMessage instead of the expected handshake response — a protocol-level mismatch.

Solutions

  1. Match client and server SignalR versions (same major).
  2. If the server sends a handshake error, capture it server-side via its logs and resolve the underlying configuration.
  3. Retry the connection; a transient mid-stream condition can clear.
  4. Verify no proxy is reordering or re-framing the message stream.
Defensive patterns

Strategy: validation

Validate before calling

function isHandshakeResponse(obj: unknown): boolean {
  return typeof obj === "object" && obj !== null && !("type" in obj);
}

Type guard

function isHandshakeResponseMessage(v: unknown): v is { error?: string } {
  return typeof v === "object" && v !== null && !("type" in v);
}

Try / catch

try { await connection.start(); }
catch (e) {
  if (e instanceof Error && e.message.includes("Expected a handshake response")) {
    console.error("Protocol mismatch: client/server SignalR versions differ.");
  }
  throw e;
}

Prevention

When it happens

Trigger: parseHandshakeResponse decodes a JSON object that has a 'type' property. Happens when the server speaks an incompatible protocol version that puts an error in a typed message, when the client connected mid-stream and received a hub message first, or when the handshake response was malformed by a proxy.

Common situations: Client/server protocol version mismatch (e.g. an older server that emits a typed handshake error). Connecting to a hub that is mid-broadcast. A man-in-the-middle or debug proxy that re-frames messages.

Understand the failure class

Related errors


AI-assisted analysis of dotnet/aspnetcore@3600ca084e (2026-08-11). Data as JSON: /api/errors/468a038e6c1aa921. Report an issue: GitHub.

Appendix: source

Thrown at src/SignalR/clients/ts/signalr/src/HandshakeProtocol.ts:61

        } else {
            const textData: string = data;
            const separatorIndex = textData.indexOf(TextMessageFormat.RecordSeparator);
            if (separatorIndex === -1) {
                throw new Error("Message is incomplete.");
            }

            // content before separator is handshake response
            // optional content after is additional messages
            const responseLength = separatorIndex + 1;
            messageData = textData.substring(0, responseLength);
            remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null;
        }

        // At this point we should have just the single handshake message
        const messages = TextMessageFormat.parse(messageData);
        const response = JSON.parse(messages[0]);
        if (response.type) {
            throw new Error("Expected a handshake response from the server.");
        }
        const responseMessage: HandshakeResponseMessage = response;

        // multiple messages could have arrived with handshake
        // return additional data to be parsed as usual, or null if all parsed
        return [remainingData, responseMessage];
    }
}

View on GitHub (pinned to 3600ca084e)