Routing
A client receives a notification only when both conditions are true:
- The client has joined the audience the notification was sent to
- 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:
| Audience | Who belongs to it | How a client joins |
|---|---|---|
| Global | Every connected client | Automatically on connect |
| User | All connections of one authenticated user | Automatically on connect |
| Connection | A single specific connection | Automatically on connect |
| Tenant | Clients that explicitly joined the tenant | Client calls Subscribe |
| Group | Clients that explicitly joined the named group | Client 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
typestring 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 & 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