Documentation
Sign inGet started

Connection lifecycle and reconnection

A Realtime connection is one WebSocket. Network changes, sleeping devices, and infrastructure restarts can close it. The client reconnects automatically, while your app can show the current connection state.

The six states

bird.connection.state in the browser client, and bird.connectionState in Swift and Kotlin, is always one of these (Kotlin spells them as ConnectionState constants, so unavailable reads ConnectionState.UNAVAILABLE):
StateWhat it means
initializedThe client exists and has not opened a socket yet.
connectingA socket is opening, or is open and waiting for the handshake.
connectedThe handshake landed. The connection has an ID and can subscribe.
unavailableThe connection dropped and a reconnect is scheduled.
disconnectedYou called disconnect(). Nothing is scheduled.
failedThe server refused this connection. The client does not retry.
Bind state_change to see every transition, or bind a single state name when only one matters:
import { BirdRealtime } from "@messagebird/realtime";

const bird = new BirdRealtime({ appKey: "your-app-key", region: "us1" });

bird.connection.bind("state_change", ({ previous, current }) => {
  console.log(previous, "->", current);
});

bird.connection.bind("unavailable", () => showReconnectingBanner());
bird.connection.bind("connected", () => hideReconnectingBanner());
Swift and Kotlin use one observer for every transition, so branch on current. Handlers run on the main queue in Swift and on the Android main looper in Kotlin when available. Pass a deliveryQueue or Executor to use another execution context.
An open socket remains in connecting until the server's handshake arrives. The handshake sets bird.connection.connectionId before the state becomes connected, so authorization can sign that ID. If the handshake omits the ID, the client reports an error and closes the socket.

Automatic reconnection

After an unexpected drop, the client waits before opening a new socket. The delay uses full-jitter exponential backoff with a one-second base and a 30-second cap. Each successful handshake resets the backoff.
The close code decides which of three things happens:
Close codeBehavior
4000 to 4099Refused. No retry, state goes to failed.
4200 to 4299Retry immediately.
Everything elseState goes to unavailable, then retry with backoff.
Network failures use the backoff path. Handle refusal codes in your UI because these terminal failures do not reconnect automatically.

The stale-connection check

A socket can appear open after its network path fails. The client sends a ping after the activity timeout, which defaults to 120 seconds unless the handshake supplies another value. If no pong arrives within the 30-second default pong timeout, the client closes the socket with a retry-immediately code.

What happens to your channels

Subscriptions belong to the connection, so a drop removes them. The client retains each channel object, re-subscribes after the next connected state, and keeps its existing handlers.
The client calls your authEndpoint again for each private and presence channel using the new connection ID. It cannot reuse the old signature because that signature includes the previous connection ID. After one signin() call, the client also signs in on each new connection and reports later failures through signin_error.
bird.connection.bind("signin_error", ({ message }) => {
  console.warn("connection has no identity:", message);
});
While a channel is reconnecting, channel.trigger returns false without sending the client event. Check channel.subscribed, or send after bird:subscription_succeeded, which fires after every successful re-subscription.
const room = bird.subscribe("presence-room-1");

room.bind("bird:subscription_succeeded", ({ members }) => {
  renderMembers(members);
});
Realtime does not replay events published while a client is disconnected. If a view must recover after a gap, fetch its current state on bird:subscription_succeeded and apply later events from there.

Disconnecting on purpose

bird.disconnect() closes the socket without scheduling a reconnect. Use it when a signed-out or inactive client no longer needs live data. The client retains channels, so a later bird.connect() reopens the socket and re-subscribes.
bird.disconnect(); // state: disconnected, no reconnect
bird.connect(); // reopens and re-subscribes
On mobile, disconnect when the relevant view or activity stops and reconnect when it resumes. Channel objects and bindings remain available.

Errors on the connection versus errors on a channel

Server errors that belong to one channel arrive on that channel. A refused subscription is the usual one, and an authorizer that returns 403 shows up here:
room.bind("bird:subscription_error", (err) => {
  console.warn("could not join:", err);
});
Everything the wire does not attribute to a channel arrives on the connection instead:
bird.connection.bind("error", ({ code, message }) => {
  console.warn(code, message);
});

Handle close code 4009

Code 4009 is in the no-retry band, so the connection enters failed. Check the reason to distinguish these situations:
  • A terminated member. Your backend called the disconnect API for this member, so every connection they hold was closed. See Terminating member connections.
  • A connection that never authorized. The app requires authorized connections and this one did not authorize in time. See Requiring authorized connections.
The client does not reconnect automatically, so your app decides what happens next:
bird.connection.bind("error", ({ code }) => {
  if (code === 4009) showSignedOutScreen();
});
A signed-out state is appropriate when authentication has ended. Calling bird.connect() starts a new connection and repeats authorization, so reconnect only after the caller's authorization state changes.

Next steps