Control who connects and subscribes using your existing session rules.
Deliver private order updates, account alerts, and workspace events to browser and mobile clients without exposing backend credentials in client code. Your own auth endpoint checks the caller's session and signs private or presence channel subscriptions per connection, while end-to-end encryption seals sensitive payloads before they leave your server.
auth.ts
200 · signed403 · Forbidden200 · signed200 · signed// Your endpoint. The only place the app secret lives.app.post("/bird/auth", async (req, res) => { const { connection_id, channel_name } = req.body; const user = await session(req); if (!mayJoin(user, channel_name)) return res.sendStatus(403); // Signs <connection_id>:<channel_name>[:<member_data>] with the app // secret, and adds shared_secret on an encrypted channel. res.json( await bird.realtime.authorizeChannel({ connectionId: connection_id, channelName: channel_name, memberData: JSON.stringify({ member_id: user.id, member_info: { name: user.name }, }), }), );});
POST/bird/auth
Response body
{ "auth": "d0e95a856ddc1b09d4c8:8f9a2c71e4…", "member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Maya Chen\"}}" }
auth.ts
200 · signed403 · Forbidden200 · signed200 · signed// Your endpoint. The only place the app secret lives.app.post("/bird/auth", async (req, res) => { const { connection_id, channel_name } = req.body; const user = await session(req); if (!mayJoin(user, channel_name)) return res.sendStatus(403); // Signs <connection_id>:<channel_name>[:<member_data>] with the app // secret, and adds shared_secret on an encrypted channel. res.json( await bird.realtime.authorizeChannel({ connectionId: connection_id, channelName: channel_name, memberData: JSON.stringify({ member_id: user.id, member_info: { name: user.name }, }), }), );});
POST/bird/auth
Response body
{ "auth": "d0e95a856ddc1b09d4c8:8f9a2c71e4…", "member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Maya Chen\"}}" }
auth.ts
200 · signed403 · Forbidden200 · signed200 · signed// Your endpoint. The only place the app secret lives.app.post("/bird/auth", async (req, res) => { const { connection_id, channel_name } = req.body; const user = await session(req); if (!mayJoin(user, channel_name)) return res.sendStatus(403); // Signs <connection_id>:<channel_name>[:<member_data>] with the app // secret, and adds shared_secret on an encrypted channel. res.json( await bird.realtime.authorizeChannel({ connectionId: connection_id, channelName: channel_name, memberData: JSON.stringify({ member_id: user.id, member_info: { name: user.name }, }), }), );});
POST/bird/auth
Response body
{ "auth": "d0e95a856ddc1b09d4c8:8f9a2c71e4…", "member_data": "{\"member_id\":\"u_42\",\"member_info\":{\"name\":\"Maya Chen\"}}" }
Ship a public key to clients
while keeping signing secrets on your server.
An app on the Bird Realtime API separates client connection credentials from backend authority. Browsers and mobile apps open WebSockets with a public app key that is safe to embed in client bundles, while your backend holds the secret used to publish events and sign subscription tokens. When your security policy calls for credential rotation, you can create a second key pair, roll it out across your services, and revoke the previous pair without dropping active users.
Protect connections, channels, and payloads at each layer
Verify who can open a socket, authorize which channels they join, encrypt sensitive event data, and cut off access when a session ends.
- 01
Signed channel subscriptions.
When a browser or mobile client subscribes to a private or presence channel, your backend checks the user's session and signs an HMAC-SHA256 token tied to that specific connection ID and channel name. Binding the signature to the socket ID prevents a token from being replayed on another connection, and presence signatures also lock in the member identity displayed to the room.
- 02
Authenticated WebSocket connections.
Because your public app key lives in client code, any visitor could try to open an idle WebSocket. Enabling authorized connections requires a new socket to authenticate through your backend within 30 seconds, either by signing in or by subscribing to a private channel. Unverified sockets close automatically and do not count toward your connection quota.
- 03
End-to-end encrypted payloads.
For regulated or confidential updates, the server SDK encrypts event payloads locally using a 32-byte master key that stays on your infrastructure. Each encrypted channel derives its own decryption key, so approving a client for one confidential stream does not grant access to another, and intermediate servers see ciphertext in transit.
- 04
Instant session revocation.
When a user logs out, changes a password, or loses workspace access, a single server call disconnects their member identity across open browser tabs and mobile devices. Clients treat the termination as final instead of reconnecting in a loop, and your auth endpoint can reject future sign-in attempts once the session is revoked.
Encrypted channels
Seal sensitive payloads before they leave your server.
Configure a 32-byte master key in the server SDK to encrypt confidential events inside your own process before publishing. Bird routes the sealed envelope to subscribers, and your authorization endpoint delivers the channel's derived decryption key to approved browser clients. You can also combine encryption with last-event caching so newly connected clients receive the latest state as ciphertext, while keeping channel and event names generic since routing metadata stays unencrypted.
const bird = new BirdClient({
apiKey: process.env.BIRD_API_KEY!,
realtime: {
key: process.env.BIRD_REALTIME_KEY,
secret: process.env.BIRD_REALTIME_SECRET,
// 32 random bytes, yours alone. Never sent to Bird.
encryptionMasterKey: process.env.BIRD_REALTIME_MASTER_KEY,
},
});
// Sealed in your process. The edge forwards ciphertext.
await bird.realtime.publish(APP_ID, {
event: "order.updated",
channels: ["private-encrypted-orders"],
data: { order_id: "ord_123", status: "shipped" },
});
Isolate tenant streams and validate state on your server.
Connection authentication and channel authorization solve different parts of access control. Requiring authorized connections blocks anonymous sockets, while private and presence channels restrict which signed-in user or tenant can read a specific stream. Keep customer-specific updates on private channels verified by your auth endpoint, use client-to-client events for ephemeral UI signals like typing indicators, and route state-changing actions through your backend.
Pin traffic to a region and isolate environments by app.
Choose a dedicated region when you create a Realtime app to keep WebSocket connections and published events close to your users, even if your workspace home region is elsewhere. Because each app runs in its own isolated namespace with separate credentials and quotas, provisioning separate apps for development, staging, and production keeps test broadcasts out of live customer channels.
Implementation guides for your backend.
Follow authorizing channels for the webhook request format and SDK signing helpers. Read requiring authorized connections to enforce the 30-second connection handshake, encrypted channels to configure master keys and key rotation, and terminating member connections to wire up user sign-in and remote disconnect.
Keep building
with Realtime.
Combine channel authorization with live presence, state caching, and backend webhooks using the same app credentials.
Channels
Route public broadcasts, private streams, encrypted payloads, and cached state by channel prefix.
→Presence
Show who is online across tabs and devices, track departures, and message individual users directly.
→Webhooks
Trigger backend workflows when channels become active, members leave, or a cache needs warming.
→Scale without
losing control.
Organize teams in workspaces, control API access, and trace changes through audit logs.
Organization
Harbor
Access stays in its workspace.
Production/API keys
Harbor backend
ActiveAlex Lee
Admin
Today · UTC
Audit log
Organization
Harbor
Access stays in its workspace.
Production/API keys
Harbor backend
ActiveAlex Lee
Admin
Today · UTC
Audit log
Organization
Harbor
Access stays in its workspace.
Production/API keys
Harbor backend
ActiveAlex Lee
Admin
Today · UTC
Audit log