{"record":{"id":"ea5c6015c1357acc","repo":"OrchardCMS/OrchardCore","slug":"expected-a-handshake-response-from-the-server","errorCode":null,"errorMessage":"Expected a handshake response from the server.","messagePattern":"Expected a handshake response from the server\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js","lineNumber":902,"sourceCode":"            remainingData = (binaryData.byteLength > responseLength) ? binaryData.slice(responseLength).buffer : null;\n        }\n        else {\n            const textData = data;\n            const separatorIndex = textData.indexOf(TextMessageFormat.RecordSeparator);\n            if (separatorIndex === -1) {\n                throw new Error(\"Message is incomplete.\");\n            }\n            // content before separator is handshake response\n            // optional content after is additional messages\n            const responseLength = separatorIndex + 1;\n            messageData = textData.substring(0, responseLength);\n            remainingData = (textData.length > responseLength) ? textData.substring(responseLength) : null;\n        }\n        // At this point we should have just the single handshake message\n        const messages = TextMessageFormat.parse(messageData);\n        const response = JSON.parse(messages[0]);\n        if (response.type) {\n            throw new Error(\"Expected a handshake response from the server.\");\n        }\n        const responseMessage = response;\n        // multiple messages could have arrived with handshake\n        // return additional data to be parsed as usual, or null if all parsed\n        return [remainingData, responseMessage];\n    }\n}\n\n;// CONCATENATED MODULE: ./src/IHubProtocol.ts\n// Licensed to the .NET Foundation under one or more agreements.\n// The .NET Foundation licenses this file to you under the MIT license.\n/** Defines the type of a Hub Message. */\nvar MessageType;\n(function (MessageType) {\n    /** Indicates the message is an Invocation message and implements the {@link @microsoft/signalr.InvocationMessage} interface. */\n    MessageType[MessageType[\"Invocation\"] = 1] = \"Invocation\";\n    /** Indicates the message is a StreamItem message and implements the {@link @microsoft/signalr.StreamItemMessage} interface. */\n    MessageType[MessageType[\"StreamItem\"] = 2] = \"StreamItem\";","sourceCodeStart":884,"sourceCodeEnd":920,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js#L884-L920","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Ensure the server responds to the handshake request with `{}` (no type field) framed with 0x1E before any hub messages","Complete the client handshake before the server sends any hub invocations or pings","Check for message replay/buffering in proxies and disable it for SignalR connections","If writing a custom server, follow the SignalR handshake spec: first outbound message must be the handshake response"],"exampleFix":"// before (custom server)\nawait ws.send(JSON.stringify({ type: 1, target: 'init' }) + '\\u001e'); // sent before handshake reply\n// after\nawait ws.send('{}\\u001e'); // handshake response first\nawait ws.send(JSON.stringify({ type: 1, target: 'init' }) + '\\u001e');","handlingStrategy":"try-catch","validationCode":"const parsed = JSON.parse(firstMessage); if (parsed && typeof parsed.type === 'number') fail('first message must be handshake response');","typeGuard":"const isHandshakeResponse = (m) => m !== null && typeof m === 'object' && m.type === undefined;","tryCatchPattern":"try { await connection.start(); } catch (e) { if (e.message.includes('Expected a handshake response')) { inspectServerHandshake(); } else throw e; }","preventionTips":["Server must send {} + 0x1E as its first message","Never send hub messages before handshake completion","Disable proxy message replay for SignalR paths"],"tags":["protocol","handshake","signalr","server"],"backgroundTag":"unexpected-api-response-shape","analyzedSha":"4306c0717fe573f6fca1b4955909ddab6a192807","analyzedAt":"2026-09-13T17:41:05.024Z","contentChangedAt":"2026-09-13T17:41:05.024Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}