{"record":{"id":"7af410b37a1f929e","repo":"OrchardCMS/OrchardCore","slug":"negotiation-can-only-be-skipped-when-using-the-websocket","errorCode":null,"errorMessage":"Negotiation can only be skipped when using the WebSocket transport directly.","messagePattern":"Negotiation can only be skipped when using the WebSocket transport directly\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js","lineNumber":2834,"sourceCode":"        }\n    }\n    async _startInternal(transferFormat) {\n        // Store the original base url and the access token factory since they may change\n        // as part of negotiating\n        let url = this.baseUrl;\n        this._accessTokenFactory = this._options.accessTokenFactory;\n        this._httpClient._accessTokenFactory = this._accessTokenFactory;\n        try {\n            if (this._options.skipNegotiation) {\n                if (this._options.transport === HttpTransportType.WebSockets) {\n                    // No need to add a connection ID in this case\n                    this.transport = this._constructTransport(HttpTransportType.WebSockets);\n                    // We should just call connect directly in this case.\n                    // No fallback or negotiate in this case.\n                    await this._startTransport(url, transferFormat);\n                }\n                else {\n                    throw new Error(\"Negotiation can only be skipped when using the WebSocket transport directly.\");\n                }\n            }\n            else {\n                let negotiateResponse = null;\n                let redirects = 0;\n                do {\n                    negotiateResponse = await this._getNegotiationResponse(url);\n                    // the user tries to stop the connection when it is being started\n                    if (this._connectionState === \"Disconnecting\" /* ConnectionState.Disconnecting */ || this._connectionState === \"Disconnected\" /* ConnectionState.Disconnected */) {\n                        throw new AbortError(\"The connection was stopped during negotiation.\");\n                    }\n                    if (negotiateResponse.error) {\n                        throw new Error(negotiateResponse.error);\n                    }\n                    if (negotiateResponse.ProtocolVersion) {\n                        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.\");\n                    }\n                    if (negotiateResponse.url) {","sourceCodeStart":2816,"sourceCodeEnd":2852,"githubUrl":"https://github.com/OrchardCMS/OrchardCore/blob/4306c0717fe573f6fca1b4955909ddab6a192807/src/OrchardCore.Modules/OrchardCore.SignalR/wwwroot/Scripts/signalr.js#L2816-L2852","documentation":"HttpConnection.startInternal only skips server negotiation when options.skipNegotiation is true AND the resolved transport is exactly WebSockets. Any other combination (skipNegotiation with SSE or LongPolling, or with an unresolved/default transport) throws this error, because only WebSockets can connect directly to a hub endpoint without a negotiate handshake.","triggerScenarios":"Calling withUrl(url, { skipNegotiation: true }) while options.transport is HttpTransportType.ServerSentEvents, HttpTransportType.LongPolling, left as default (auto-negotiate), or set to a non-WebSocket combination.","commonSituations":"Developers enabling skipNegotiation to work around sticky-session/load-balancer issues without pinning transport: WebSockets; Azure App Service or environments where SSE is the negotiated transport; copied config from a WebSockets-only sample applied to an auto transport.","solutions":["Set transport: signalR.HttpTransportType.WebSockets together with skipNegotiation: true.","Alternatively remove skipNegotiation: true and let the standard negotiate flow run.","Ensure the server also has negotiation enabled if you keep skipNegotiation off.","If the server only supports non-WebSocket transports, do not skip negotiation."],"exampleFix":"// before\nnew signalR.HubConnectionBuilder()\n  .withUrl(\"/hub\", { skipNegotiation: true }) // transport not pinned\n  .build();\n// after\nnew signalR.HubConnectionBuilder()\n  .withUrl(\"/hub\", {\n    skipNegotiation: true,\n    transport: signalR.HttpTransportType.WebSockets\n  })\n  .build();","handlingStrategy":"validation","validationCode":"function assertSkipNegotiationValid(options) {\n  if (options.skipNegotiation && options.transport !== signalR.HttpTransportType.WebSockets) {\n    throw new Error(\"skipNegotiation requires transport: HttpTransportType.WebSockets\");\n  }\n}","typeGuard":"function canSkipNegotiation(options) {\n  return options.skipNegotiation === true &&\n         options.transport === signalR.HttpTransportType.WebSockets;\n}","tryCatchPattern":"try {\n  await connection.start();\n} catch (err) {\n  if (err.message.includes(\"Negotiation can only be skipped\")) {\n    console.error(\"Add transport: HttpTransportType.WebSockets when skipNegotiation is true\");\n  } else { throw err; }\n}","preventionTips":["Never set skipNegotiation: true without also pinning transport to WebSockets.","Remember the server also needs negotiation disabled policy or matching config.","Document in your connection factory why skipNegotiation is used.","Prefer the default negotiate flow unless you specifically need to avoid it."],"tags":["signalr","configuration","negotiation","websockets"],"backgroundTag":"conflicting-config-options","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"}