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.
Related resources
Continue with the documentation, guides and examples for this topic. Resources are in English.
Explore the capabilityRealtimeFollow the learning pathBuild your first integrationImplementation guideSend your first realtime event
Try the practice and get an implementation brief