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

LanguageLibraryMaintained by
JavaScript / TypeScriptSignalR JavaScript clientMicrosoft
C# / .NETSignalR .NET clientMicrosoft
SwiftSignalR Swift clientMicrosoft
JavaSignalR Java clientMicrosoft
PythonsignalrcoreCommunity
C++SignalR-Client-CppMicrosoft
Rustsignalrs-clientCommunity
Flutter / Dartsignalr_netcoreCommunity
GosignalrCommunity

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()
Only register handlers you need right away (for Global, User, and Connection audiences) before calling 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()
ApproachSticky sessionsNegotiate roundtripNotes
skipNegotiation: true (recommended)Not requiredNoWebSocket transport only. Best performance
Default (with negotiate)RequiredYesFalls 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.