Connecting
Notiway is built on SignalR, so any SignalR-compatible client can connect to the gateway. Clients connect to the /notifications hub endpoint over WebSocket.
For a quick hands-on walkthrough, see the Start Now guide. For deeper details on connection lifecycle, reconnection, audiences, and authentication, see Connection Management.
Client Libraries
| Language | Library | Maintained by |
|---|---|---|
| JavaScript / TypeScript | SignalR JavaScript client | Microsoft |
| C# / .NET | SignalR .NET client | Microsoft |
| Swift | SignalR Swift client | Microsoft |
| Java | SignalR Java client | Microsoft |
| Python | signalrcore | Community |
| C++ | SignalR-Client-Cpp | Microsoft |
| Rust | signalrs-client | Community |
| Flutter / Dart | signalr_netcore | Community |
| Go | signalr | Community |
Basic Connection Example
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://your-gateway/notifications")
.withAutomaticReconnect()
.build();
connection.on("your-notification-type", (notification) => {
console.log("Received:", notification.body);
});
await connection.start();var connection = new HubConnectionBuilder()
.WithUrl("https://your-gateway/notifications")
.WithAutomaticReconnect()
.Build();
connection.On<dynamic>("your-notification-type", notification =>
{
Console.WriteLine($"Received: {notification.body}");
});
await connection.StartAsync();final connection = HubConnectionBuilder()
.withUrl("https://your-gateway/notifications")
.withAutomaticReconnect()
.build();
connection.on("your-notification-type", (arguments) {
final notification = arguments?[0];
print("Received: ${notification['body']}");
});
await connection.start();let connection = HubConnectionBuilder()
.withUrl(url: "https://your-gateway/notifications")
.withAutoReconnect()
.build()
connection.on("your-notification-type") { arguments in
let notification = arguments[0] as! [String: Any]
print("Received: \(notification["body"]!)")
}
try await connection.start()val connection = HubConnectionBuilder.create("https://your-gateway/notifications")
.withAutoReconnect()
.build()
connection.on("your-notification-type") { notification: Map<String, Any> ->
println("Received: ${notification["body"]}")
}
connection.start()start(). For other audiences (Tenant, Group), register their handlers just before calling Subscribe. Persisted notifications are replayed at the moment of connect or subscribe, so any handler not yet registered will miss them.Transport Configuration
By default, SignalR clients perform a negotiate handshake (an HTTP POST to /negotiate) before upgrading to a WebSocket connection. The negotiate request returns a connection ID that must be used for the subsequent WebSocket upgrade. This means both requests must reach the same server instance, which requires sticky sessions on your load balancer.
To avoid this, you can skip the negotiate step entirely by setting skipNegotiation: true and forcing the WebSocket transport. The client will connect directly via WebSocket without the initial HTTP roundtrip.
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://your-gateway/notifications", {
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets
})
.withAutomaticReconnect()
.build();var connection = new HubConnectionBuilder()
.WithUrl("https://your-gateway/notifications", options =>
{
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
})
.WithAutomaticReconnect()
.Build();final connection = HubConnectionBuilder()
.withUrl(
"https://your-gateway/notifications",
options: HttpConnectionOptions(
skipNegotiation: true,
transport: HttpTransportType.webSockets,
),
)
.withAutomaticReconnect()
.build();let connection = HubConnectionBuilder()
.withUrl(url: "https://your-gateway/notifications",
transportType: .webSockets,
skipNegotiation: true)
.withAutoReconnect()
.build()val connection = HubConnectionBuilder.create("https://your-gateway/notifications")
.shouldSkipNegotiate(true)
.withTransport(TransportEnum.WEBSOCKETS)
.withAutoReconnect()
.build()| Approach | Sticky sessions | Negotiate roundtrip | Notes |
|---|---|---|---|
skipNegotiation: true (recommended) | Not required | No | WebSocket transport only. Best performance |
| Default (with negotiate) | Required | Yes | Falls back to Long Polling if WebSocket is unavailable |
Use skipNegotiation: true for best performance in most deployments. It eliminates the negotiate roundtrip and removes the need for sticky sessions.
Keep the default negotiate flow if your clients may be behind corporate proxies or firewalls that block WebSocket connections. With negotiate enabled, SignalR can fall back to Long Polling automatically, but you will need sticky sessions on your load balancer.
Authentication
WebSocket connections do not support custom HTTP headers after the initial handshake. To pass an authentication token, use the accessTokenFactory option. The token is sent as a query string parameter during the WebSocket upgrade request.
const connection = new signalR.HubConnectionBuilder()
.withUrl("https://your-gateway/notifications", {
accessTokenFactory: () => getAccessToken(),
skipNegotiation: true,
transport: signalR.HttpTransportType.WebSockets
})
.withAutomaticReconnect()
.build();var connection = new HubConnectionBuilder()
.WithUrl("https://your-gateway/notifications", options =>
{
options.AccessTokenProvider = () => Task.FromResult(GetAccessToken());
options.SkipNegotiation = true;
options.Transports = HttpTransportType.WebSockets;
})
.WithAutomaticReconnect()
.Build();final connection = HubConnectionBuilder()
.withUrl(
"https://your-gateway/notifications",
options: HttpConnectionOptions(
accessTokenFactory: () async => getAccessToken(),
skipNegotiation: true,
transport: HttpTransportType.webSockets,
),
)
.withAutomaticReconnect()
.build();let connection = HubConnectionBuilder()
.withUrl(url: "https://your-gateway/notifications",
transportType: .webSockets,
skipNegotiation: true)
.withAccessTokenFactory { getAccessToken() }
.withAutoReconnect()
.build()val connection = HubConnectionBuilder.create("https://your-gateway/notifications")
.withAccessTokenProvider { getAccessToken() }
.shouldSkipNegotiate(true)
.withTransport(TransportEnum.WEBSOCKETS)
.withAutoReconnect()
.build()See Auth Plugins for details on configuring authentication on the server side.