Presence channels
A channel whose name starts with presence- uses private-channel authorization and adds member identity. Your backend assigns each subscriber a member_id. Subscribers receive the current member list and events when members join or leave.
Use presence channels for online rosters, document viewers, typing indicators, or collaborative cursors.
Subscribing
const room = bird.subscribe("presence-room-1");
room.bind("bird:subscription_succeeded", () => {
console.log("me:", room.myId);
console.log("here now:", [...room.members.values()]);
});
room.bind("bird:member_added", (member) => console.log("joined", member.member_id));
room.bind("bird:member_removed", (member) => console.log("left", member.member_id));guard let room = bird.subscribe("presence-room-1") as? PresenceChannel else { return }
room.bind(BirdProtocol.Event.subscriptionSucceeded) { _ in
print("me:", room.myId ?? "")
print("here now:", room.members.keys)
}
room.bind(BirdProtocol.Event.memberAdded) { member in
print("joined", (member as? Member)?.memberId ?? "")
}
room.bind(BirdProtocol.Event.memberRemoved) { member in
print("left", (member as? Member)?.memberId ?? "")
}val room = bird.subscribe("presence-room-1")
if (room is PresenceChannel) {
room.bind(BirdProtocol.Event.SUBSCRIPTION_SUCCEEDED) {
println("me: ${room.myId}")
println("here now: ${room.members.keys}")
}
room.bind(BirdProtocol.Event.MEMBER_ADDED) { data ->
println("joined ${data?.jsonObject?.get("member_id")?.jsonPrimitive?.content}")
}
room.bind(BirdProtocol.Event.MEMBER_REMOVED) { data ->
println("left ${data?.jsonObject?.get("member_id")?.jsonPrimitive?.content}")
}
}In Swift and Kotlin, subscribe returns a base channel type. Cast it to PresenceChannel before accessing myId or members. Swift provides a member map for bird:subscription_succeeded and a Member for join and leave events. Kotlin provides each event as JsonElement. In both clients, read the current roster from room.members.
bird:subscription_succeeded fires when the subscription completes. At that point, room.members contains the current roster and room.myId contains your member_id. Render this initial roster before applying later join and leave events.
Authorization is the same flow as a private channel with one addition: your endpoint returns member_data, a JSON string carrying member_id and optional member_info, and it must sign that exact string. See Authorizing channels.
Use member_info safely
Every channel member receives member_info. Use it for small public profile data such as a display name or avatar URL. Do not include email addresses, authorization roles, or other sensitive data.
Your backend assigns member_id and member_info in its signed authorization response. The client can request a subscription but cannot choose the signed identity.
Members and connections
One member can hold several connections. For example, three open tabs produce one member and three connections:
| What happens | Member event | Channel connection count |
|---|---|---|
| First tab subscribes | bird:member_added | 1 |
| Second tab subscribes | none | 2 |
| Second tab closes | none | 1 |
| Last tab closes | bird:member_removed | 0 |
room.members represents identities rather than sessions. To receive the number of open connections, enable Connection counting and Connection count events, then bind bird:connection_count.
Reading presence from your server
Two server-side reads exist, and neither requires a subscription:
- List channel members returns the member_id values currently subscribed to a presence channel.
- Get a channel with include=member_count returns how many members are present, without listing them.
member_count is presence-only. Asking for it on a public or private channel is a validation error, because those channels have no members.
You can also request it while publishing, which returns each target channel's state at that moment. See Publishing events.
Next steps
- Authorizing channels covers member_data and the string your backend signs.
- Private channels are the same authorization without identity.
- Excluding event recipients keeps a client from receiving its own change.