Routing

A client receives a notification only when both conditions are true:

  1. The client has joined the audience the notification was sent to
  2. The client has subscribed to the notification type

If either condition is not met, the notification is not delivered to that client.


Condition 1 — Joining an Audience

An audience is a named group of clients. When a producer publishes a notification, it targets a specific audience. Only clients that have joined that audience will receive it.

There are five audience types:

AudienceWho belongs to itHow a client joins
GlobalEvery connected clientAutomatically on connect
UserAll connections of one authenticated userAutomatically on connect
ConnectionA single specific connectionAutomatically on connect
TenantClients that explicitly joined the tenantClient calls Subscribe
GroupClients that explicitly joined the named groupClient calls Subscribe

Global, User, and Connection audiences are joined automatically by the gateway. The client does not need to do anything. Tenant and Group audiences require the client to explicitly call the Subscribe hub method.

  flowchart TD
    subgraph Client[" "]
        CH["Alice"]@{ icon: "mdi:account-tie", form: "circle" }

        subgraph Auto[" "]
            AutoH["⚡ Joined automatically on connect"]
            A1["🌐 global"]
            A2["👤 user:alice"]
            A3["🔌 conn:abc-123"]
            AutoH --- A1 & A2 & A3
        end

        subgraph Manual[" "]
            ManualH["✋ Joined explicitly via Subscribe"]
            A4["🏢 tenant:acme"]
            A5["👥 group:admins"]
            ManualH --- A4 & A5
        end

        CH --> Auto
        CH --> Manual
    end

    style CH font-size:24px,font-weight:bold
    style AutoH font-size:18px,font-weight:bold
    style ManualH font-size:18px,font-weight:bold
    style A1 font-size:18px
    style A2 font-size:18px
    style A3 font-size:18px
    style A4 font-size:18px
    style A5 font-size:18px

A notification addressed to tenant:acme reaches this client. A notification addressed to tenant:other-corp does not.

Persisted notifications on join: When a client joins a Tenant or Group audience, Notiway immediately replays all persisted notifications for that audience that haven’t expired yet. The client catches up on everything it missed before joining. The same applies on initial connect for Global and User audiences.

Order matters: Register your type handlers before joining an audience. Persisted notifications are replayed at the moment of joining. If a handler is not yet registered, those replayed notifications will be delivered but silently ignored.

See Connection Management for how to join and leave audiences.


Condition 2 — Subscribing to a Notification Type

Every notification has a type field, a free-form string defined by the producer (e.g. "order-placed", "alert", "user-banned").

On the client side, you subscribe to a type by registering a handler for it:

connection.on("order-placed", (notification) => {
    console.log("New order:", notification);
});
connection.On<NotificationDto>("order-placed", notification =>
{
    Console.WriteLine($"New order: {notification}");
});
connection.on("order-placed", (arguments) {
    print("New order: ${arguments![0]}");
});
connection.on(method: "order-placed") { args in
    print("New order: \(args)")
}
connection.on("order-placed", { args ->
    println("New order: ${args[0]}")
}, Any::class.java)

The gateway delivers the notification by invoking the handler registered under that exact type name. If the client has not subscribed to "order-placed", the notification is delivered to the connection but nothing happens. This is not an error.

The type string is case-sensitive. "order-placed" and "Order-Placed" are treated as different types.


Both Conditions Together

  flowchart TD
    P["Producer publishes notification"]
    P --> Q1{"Has client joined the target audience?"}
    Q1 -->|No| R1["❌ Not delivered"]
    Q1 -->|Yes| Q2{"Has client subscribed to this notification type?"}
    Q2 -->|No| R2["⚠️ Delivered, ignored"]
    Q2 -->|Yes| R3["✅ Handler is called"]

The gateway controls audience membership. Notification type subscriptions are managed entirely by the client. Subscribe to the types you care about and ignore the rest.


Practical Example

Two clients are connected. A producer publishes two notifications targeted at tenant:acme-corp.

Client setup:

  graph LR
    subgraph CA["Client A (alice)"]
        CA1["✓ global"]
        CA2["✓ user:alice"]
        CA3["✓ tenant:acme-corp"]
        CA4["✓ type: order-placed"]
        CA5["✗ type: order-shipped"]
    end

    subgraph CB["Client B (bob)"]
        CB1["✓ global"]
        CB2["✓ user:bob"]
        CB3["✗ tenant:acme-corp"]
        CB4["✓ type: order-placed"]
        CB5["✓ type: order-shipped"]
    end

Producer publishes:

{ "type": "order-placed",  "routing": { "audienceType": 2, "audienceValue": "acme-corp" } }
{ "type": "order-shipped", "routing": { "audienceType": 2, "audienceValue": "acme-corp" } }

Delivery flow:

  flowchart TD
    P["Your Service"]@{ icon: "mdi:application", form: "square" }
    GW["Notiway Gateway"]@{ icon: "mdi:server", form: "square" }

    P -->|"order-placed, target: tenant:acme-corp"| GW
    P -->|"order-shipped, target: tenant:acme-corp"| GW

    subgraph Alice[" "]
        AH["Alice"]@{ icon: "mdi:account-tie", form: "circle" }
        AS["joined: tenant:acme-corp"]
        CA_op["<b>order-placed</b><br/>✓ handler registered"]
        CA_os["<b>order-shipped</b><br/>✗ no handler"]
        AH --- AS --- CA_op & CA_os
    end

    subgraph Bob[" "]
        BH["Bob"]@{ icon: "mdi:account-cowboy-hat", form: "circle" }
        BS["NOT in tenant:acme-corp"]
        CB_both["<b>order-placed &amp; order-shipped</b><br/>✗ not in audience"]
        BH --- BS --- CB_both
    end

    GW -->|"✓ in audience"| AH
    GW -->|"✗ not in audience"| BH

    CA_op --> R1["✅ Handler fires"]
    CA_os --> R2["⚠️ Silently ignored"]
    CB_both --> R3["❌ Not delivered"]

    style AH font-size:22px,font-weight:bold
    style BH font-size:22px,font-weight:bold
    style AS font-size:13px
    style BS font-size:13px,color:#cc3333
    style CA_op font-size:15px
    style CA_os font-size:15px
    style CB_both font-size:15px
    style R1 font-size:15px
    style R2 font-size:15px
    style R3 font-size:15px