Terminating member connections
Channel authorization controls subscriptions but does not assign a connection-level identity. Without that identity, signing out elsewhere does not close an existing socket or remove its channel subscriptions.
Call signin() to assign the connection a member_id from your backend. The disconnect operation can then close every connection that member holds on the app.
Use this flow after a remote sign-out, token revocation, ban, password change, or workspace-seat removal.
1. Sign the connection in
Point the client at an endpoint on your own backend and call signin() once:
import { BirdRealtime } from "@messagebird/realtime";
const bird = new BirdRealtime({
appKey: "your-app-key",
region: "us1",
memberAuthEndpoint: "/bird/auth/member",
});
const member = await bird.signin();
console.log("signed in as", member.member_id);import BirdRealtime
let bird = BirdRealtime(options: .init(
appKey: "your-app-key",
region: "us1",
memberAuthEndpoint: URL(string: "https://your-backend.example.com/bird/auth/member")
))
let member = try await bird.signin()
print("signed in as", member.memberId)import com.bird.realtime.BirdRealtime
import com.bird.realtime.BirdRealtimeOptions
val bird = BirdRealtime(
BirdRealtimeOptions(
appKey = "your-app-key",
region = "us1",
memberAuthEndpoint = "https://your-backend.example.com/bird/auth/member",
)
)
val member = bird.signin() // suspending
println("signed in as ${member.memberId}")The identity belongs to the connection. Call signin() once, and the client signs in again after each reconnect.
Signing in does not authorize channel subscriptions. Private and presence channels still call your channel authorization endpoint. A connection without a member identity can subscribe to any channel that endpoint approves. See Authorizing channels.
2. Sign the identity from your backend
The client sends the connection ID in a POST request:
Exemple de code
{ "connection_id": "26896.319537" }Your endpoint answers with the identity as a JSON string, plus a signature over that exact string:
Exemple de code
{
"auth": "your-app-key:8f9a…",
"member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Ada\"}}"
}The string to sign is <connection_id>::member::<member_data>, with two colons on each side of member. HMAC-SHA256 it with the app secret, hex-encode, and prefix the app key:
import { createHmac } from "node:crypto";
app.post("/bird/auth/member", (req, res) => {
const { connection_id } = req.body;
// Your own decision: who is this caller, and may they connect at all?
const user = getUserFromSession(req);
if (!user) return res.sendStatus(403);
const memberData = JSON.stringify({
member_id: user.id,
member_info: { name: user.name },
});
const sig = createHmac("sha256", process.env.BIRD_REALTIME_SECRET)
.update(`${connection_id}::member::${memberData}`)
.digest("hex");
res.json({ auth: `${process.env.BIRD_REALTIME_KEY}:${sig}`, member_data: memberData });
});import hmac, hashlib, json, os
def sign_in(connection_id: str, user) -> dict:
key = os.environ["BIRD_REALTIME_KEY"]
secret = os.environ["BIRD_REALTIME_SECRET"].encode()
member_data = json.dumps({"member_id": user.id, "member_info": {"name": user.name}})
to_sign = f"{connection_id}::member::{member_data}"
sig = hmac.new(secret, to_sign.encode(), hashlib.sha256).hexdigest()
return {"auth": f"{key}:{sig}", "member_data": member_data}func signIn(connectionID string, user User) (map[string]string, error) {
key := os.Getenv("BIRD_REALTIME_KEY")
secret := []byte(os.Getenv("BIRD_REALTIME_SECRET"))
memberData, err := json.Marshal(map[string]any{
"member_id": user.ID,
"member_info": map[string]string{"name": user.Name},
})
if err != nil {
return nil, err
}
mac := hmac.New(sha256.New, secret)
mac.Write([]byte(connectionID + "::member::" + string(memberData)))
return map[string]string{
"auth": key + ":" + hex.EncodeToString(mac.Sum(nil)),
"member_data": string(memberData),
}, nil
}function signIn(string $connectionId, User $user): array
{
$key = getenv('BIRD_REALTIME_KEY');
$secret = getenv('BIRD_REALTIME_SECRET');
$memberData = json_encode(['member_id' => $user->id, 'member_info' => ['name' => $user->name]]);
$sig = hash_hmac('sha256', "{$connectionId}::member::{$memberData}", $secret);
return ['auth' => "{$key}:{$sig}", 'member_data' => $memberData];
}Sign the exact member_data string you return. Re-serializing the object can change its bytes and invalidate the signature.
member_id is the value targeted by the disconnect operation. It accepts up to 128 URL-safe characters, including + : @ . _ -, but not / ? # % or whitespace. If your identifier includes unsupported characters, map it to a stable safe value before signing. The edge rejects an invalid member ID, leaving the connection without an identity that the disconnect operation can target.
member_info is optional. For connection sign-in, it is returned to that client rather than broadcast to a presence channel.
The identity may also carry a watchlist array of member ids. On apps with watchlist_events enabled, the connection is then told when those members come online or go offline; see Watchlist events.
The string signed here is deliberately not the string a presence subscription signs, so a presence authorization can never be replayed to claim an identity, even though both payloads are called member_data.
3. Disconnect the member
await bird.realtime.members.disconnect("rap_01krdgeqcxet5s7t44vh8rt9mg", "u_42");client.realtime.members.disconnect("rap_01krdgeqcxet5s7t44vh8rt9mg", "u_42")if err := client.Realtime.Members.Disconnect(context.Background(), "rap_01krdgeqcxet5s7t44vh8rt9mg", "u_42"); err != nil {
log.Fatal(err)
}$bird->realtime->members->disconnect('rap_01krdgeqcxet5s7t44vh8rt9mg', 'u_42');curl -X POST \
https://us1.platform.bird.com/v1/realtime/apps/rap_01krdgeqcxet5s7t44vh8rt9mg/members/u_42/disconnect \
-H "Authorization: Bearer $BIRD_API_KEY" \
-H "X-Realtime-Key: $BIRD_REALTIME_KEY" \
-H "X-Realtime-Secret: $BIRD_REALTIME_SECRET"The operation closes every signed-in connection for that member on the app, including other tabs and devices. It does not close anonymous connections or connections assigned to another member.
What the client sees
The connection closes with code 4009 and does not reconnect automatically:
bird.connection.bind("error", (e) => {
if (e.code === 4009) showSignedOutScreen();
});bird.onError { error in
if error.code == 4009 { showSignedOutScreen() }
}bird.onError { error ->
if (error.code == 4009) showSignedOutScreen()
}Show a signed-out state when authentication has ended. Calling bird.connect() starts a new connection that signs in through your endpoint, so reconnect only after the member's authorization state changes.
A sign-in failure does not close the socket. The client reports the failure separately and leaves the connection without a member identity. After a reconnect, the client reports this through signin_error because the original signin() promise has already resolved.
bird.connection.bind("signin_error", (e) => {
console.warn("connection has no identity:", e.message);
});bird.onSigninError { error in
print("connection has no identity:", error.message)
}bird.onSigninError { error ->
println("connection has no identity: ${error.message}")
}The connection can still receive events from authorized channels, but member-event and disconnect operations cannot target it. In the browser client, bird.signedInMember is null in this state.
Prevent reconnection
The disconnect operation closes current connections without creating a persistent block. The same client can connect again.
Revoke the session or record the ban before disconnecting the member. Your member authorization endpoint can then return 403 Forbidden if the client reconnects.
Next steps
- Authorizing channels covers the other signature your backend computes, for private and presence subscriptions.
- Disconnect a member is the full API reference for the request.
- Sending events to a member is the other member-addressed operation.
- Presence channels explain why one member can hold several connections.