Documentation
Sign inGet started

Authorizing channels

Any client holding the app key can subscribe to public channels. Two channel-name prefixes require your backend to authorize the subscription. You do not configure channels separately.
A channel named private-… requires your backend to approve each subscription. A channel named presence-… does the same and also attaches an identity to the subscriber, so everyone on the channel can see who else is there. Any other name is public.
Only your backend holds the app secret. The client asks your server to sign a specific subscription, and the Realtime edge verifies that signature before accepting it. Your server decides whether the caller may subscribe without exposing the secret to the client.

Point the client at your endpoint

Give the client an authEndpoint on your own backend:
import { BirdRealtime } from "@messagebird/realtime";

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

const room = bird.subscribe("presence-room-1");
The client calls this endpoint for each private or presence subscription, including subscriptions restored after a reconnect. Authorization applies to one connection because the signature includes its connection ID.
The browser client requires a same-origin endpoint by default. Set allowCrossOriginAuth: true to use a cross-origin authEndpoint. The browser client sends configured authHeaders only to same-origin endpoints.

What your endpoint receives and returns

The client POSTs JSON:
Codevoorbeeld
{ "connection_id": "26896.319537", "channel_name": "presence-room-1" }
Respond with the signature:
Codevoorbeeld
{ "auth": "your-app-key:8f9a…" }
For a presence channel, also return the member identity as a JSON string, the same string you signed:
Codevoorbeeld
{
  "auth": "your-app-key:8f9a…",
  "member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Ada\"}}"
}
member_id is the identity other members see and the value the disconnect operation targets. member_info is optional JSON data delivered to every channel member. It has a 1 KB limit, so include only small, non-sensitive profile data.
Authorize the caller in this endpoint using its session cookie or bearer token. Return 403 Forbidden when the caller must not join the channel. For presence channels, assign the identity in the same response.

The string you sign

Concatenate with colons, then HMAC-SHA256 with the app secret and hex-encode. Prefix the result with the app key and a colon.
Channel typeString to sign
private-…<connection_id>:<channel_name>
private-encrypted-…<connection_id>:<channel_name>
presence-…<connection_id>:<channel_name>:<member_data>
For presence channels, sign the exact member_data string you return. Re-serializing the same object can change its key order or spacing and invalidate the signature.
An encrypted channel signs like a private one, and its auth response additionally returns the channel's decryption key as shared_secret. The SDK helper adds it automatically; Encrypted channels covers the derivation and channel behavior.
Each server SDK provides an authorizeChannel helper. It signs with the configured app credentials and returns the response body without making a network request. For encrypted channels, the helper also adds shared_secret.
app.post("/bird/auth", async (req, res) => {
  const { connection_id, channel_name } = req.body;

  // Your own authorization decision goes here.
  const user = getUserFromSession(req);
  if (!user || !mayJoin(user, channel_name)) return res.sendStatus(403);

  const memberData = channel_name.startsWith("presence-")
    ? JSON.stringify({ member_id: user.id, member_info: { name: user.name } })
    : undefined;

  res.json(
    await bird.realtime.authorizeChannel({
      connectionId: connection_id,
      channelName: channel_name,
      memberData,
    }),
  );
});
The signing contract is the same in a language without an SDK: HMAC-SHA256 the string with the app secret, hex-encode it, and prefix it with the app key and a colon.

Members and connections

A member is an identity, while a connection is one open WebSocket. If someone opens your app in three tabs, one member holds three connections. member_added fires when the first connection subscribes, and member_removed fires when the last one leaves. Other connections change the channel's connection count without producing member events.

Common failures

A rejected subscription arrives as a client error. Check these common causes:
  • Invalid signature. The string you signed doesn't match. Almost always a re-serialized member_data, or a signature computed over the channel name without its private- or presence- prefix.
  • Invalid key. The key in auth belongs to a different app, or it has been revoked. Rotating keys means updating both the client's appKey and the secret your endpoint signs with.
  • Missing member data. A presence subscription arrived without member_data. Presence channels cannot be joined anonymously.
  • A 403 from your own endpoint. Your authorization decision refused, which is the intended outcome for a user who may not join.

Next steps