OrchardCMS/OrchardCore · 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 response JSON, the client verifies it is an actual handshake reply by checking that it has no `type` field; handshake responses are the only SignalR messages without one. If `type` is present, the first message received was a regular hub message (invocation, close, ping) rather than a handshake reply, so the client throws 'Expected a handshake response from the server.' This indicates protocol misuse or a desynchronized stream.

Solutions

  1. Ensure the server responds to the handshake request with `{}` (no type field) framed with 0x1E before any hub messages
  2. Complete the client handshake before the server sends any hub invocations or pings
  3. Check for message replay/buffering in proxies and disable it for SignalR connections
  4. If writing a custom server, follow the SignalR handshake spec: first outbound message must be the handshake response

Example fix

// before (custom server)
await ws.send(JSON.stringify({ type: 1, target: 'init' }) + '\u001e'); // sent before handshake reply
// after
await ws.send('{}\u001e'); // handshake response first
await ws.send(JSON.stringify({ type: 1, target: 'init' }) + '\u001e');
Defensive patterns

Strategy: try-catch

Validate before calling

const parsed = JSON.parse(firstMessage); if (parsed && typeof parsed.type === 'number') fail('first message must be handshake response');

Type guard

const isHandshakeResponse = (m) => m !== null && typeof m === 'object' && m.type === undefined;

Try / catch

try { await connection.start(); } catch (e) { if (e.message.includes('Expected a handshake response')) { inspectServerHandshake(); } else throw e; }

Prevention

When it happens

Trigger: Server sends an invocation/ping before completing the handshake; a proxy replays buffered messages out of order; a custom server implementation responds to the handshake with a typed message; connecting mid-stream to a persistent connection already delivering data.

Common situations: Custom SignalR server/bridge implementations; message replay from a misbehaving load balancer; mixing handshake and message pipelines in hand-rolled protocol code; stale long-poll connections delivering queued messages after reconnect.

Understand the failure class

Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/ea5c6015c1357acc. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js:902

            remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null;
        }
        else {
            const textData = 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 = response;
        // multiple messages could have arrived with handshake
        // return additional data to be parsed as usual, or null if all parsed
        return [remainingData, responseMessage];
    }
}

;// CONCATENATED MODULE: ./src/IHubProtocol.ts
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
/** Defines the type of a Hub Message. */
var MessageType;
(function (MessageType) {
    /** Indicates the message is an Invocation message and implements the {@link @microsoft/signalr.InvocationMessage} interface. */
    MessageType[MessageType["Invocation"] = 1] = "Invocation";
    /** Indicates the message is a StreamItem message and implements the {@link @microsoft/signalr.StreamItemMessage} interface. */
    MessageType[MessageType["StreamItem"] = 2] = "StreamItem";

View on GitHub (pinned to 4306c0717f)