Documentation
Sign inGet started

Encrypted channels

A channel whose name starts with private-encrypted- is end-to-end encrypted. Your server seals every payload before it leaves your process, and subscribers unseal it in the browser with a key your auth endpoint hands them. In between, the event is ciphertext: to the Realtime edge, to anyone inspecting traffic, and to anyone who should not be on the channel, there is nothing to read.
The key to all of it is a 32-byte master key that you generate and keep. It never appears in an API request, so there is nothing to configure on the app: the prefix in the channel name is the whole feature. The flip side of holding the only key is that we cannot help you recover it. If you lose it, you rotate to a new one and events sealed under the old key stay unreadable.
Encrypted channels authorize exactly like private channels: the same endpoint, the same signature. The one addition is that the auth response also carries the channel's decryption key, called the shared_secret, which your backend derives from the master key. A client that may not subscribe never receives the key, so access control and confidentiality are the same decision, made in the same place.

Generate a master key

32 random bytes, base64-encoded. Generate it once and store it the way you store the app secret:
Przykład kodu
openssl rand -base64 32
Give it to your server SDK as part of the realtime configuration, next to the app key and secret.

Publish an encrypted event

Publishing works like any other publish. The SDK notices the channel prefix, derives the channel's key from the master key, and seals the JSON payload locally; the request that leaves your server carries only the sealed envelope.
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({
  apiKey: process.env.BIRD_API_KEY,
  realtime: {
    key: process.env.BIRD_REALTIME_KEY,
    secret: process.env.BIRD_REALTIME_SECRET,
    encryptionMasterKey: process.env.BIRD_REALTIME_MASTER_KEY,
  },
});

await bird.realtime.publish("rap_01krdgeqcxet5s7t44vh8rt9mg", {
  event: "order.updated",
  channels: ["private-encrypted-orders"],
  data: { order_id: "ord_123", status: "shipped" },
});
One rule is enforced: an encrypted channel must be the only channel in its publish. Every encrypted channel derives its own key, so fanning one sealed payload out to other channels would hand their subscribers ciphertext they cannot open. The SDKs refuse the call locally and the API answers E23000 if one is sent anyway. To reach several channels at once, use the batch publish: each event in a batch carries one channel, and the SDK seals the encrypted ones individually.

Return the shared secret from your auth endpoint

Your auth endpoint approves encrypted subscriptions the way it approves private ones. Use the SDK's authorizeChannel helper and the response gains the shared_secret automatically whenever the channel name carries the encrypted prefix:
app.post("/bird/auth", async (req, res) => {
  const { connection_id, channel_name } = req.body;

  const user = getUserFromSession(req);
  if (!user || !mayJoin(user, channel_name)) return res.sendStatus(403);

  res.json(
    await bird.realtime.authorizeChannel({
      connectionId: connection_id,
      channelName: channel_name,
    }),
  );
});
The shared_secret is derived per channel, so approving someone for private-encrypted-orders does not let them read private-encrypted-invoices. It travels only in your auth response, from your backend to your user's browser; the subscribe frame the client then sends to the edge does not include it.

Subscribe and decrypt in the browser

The cipher lives in its own entry point, @messagebird/realtime/encrypted, so apps that never touch encrypted channels do not carry it. Import it and pass it as the client's encryption option:
Przykład kodu
import { BirdRealtime } from "@messagebird/realtime";
import { encryption } from "@messagebird/realtime/encrypted";

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

const orders = bird.subscribe("private-encrypted-orders");
orders.bind("order.updated", (data) => {
  console.log(data); // decrypted: { order_id: "ord_123", status: "shipped" }
});
Bindings receive plaintext; the decryption is invisible when everything is configured. Subscribing to an encrypted channel without the encryption option throws immediately rather than delivering ciphertext, and an auth response missing the shared_secret fails the subscription with an error naming the problem.
Encrypted channels are received in the browser client. The Swift and Kotlin clients refuse a private-encrypted- subscription with a clear error, for the same reason: a client that cannot decrypt should say so, not deliver noise.

Rotate the master key

Replace the key in your server's configuration and restart. Everything downstream follows on its own:
  1. New publishes seal under the new key.
  2. A subscribed client that receives an event it cannot open re-authorizes once, picks up the new shared_secret from your auth endpoint, and decrypts from there.
  3. An event stays undecryptable only if it was sealed under the old key and delivered after the client re-authorized, which is the moment of the switch, not an ongoing condition.
Rotation is also the recovery path for a leaked or lost key. Events published before the rotation cannot be re-sealed: anyone who held the old key could have read them, and a client that never learns the new key stops being able to read anything.

What encrypted channels do not do

  • Client events are not supported. A trigger() on an encrypted channel throws in the browser client. Encryption runs server-to-client; there is no client-to-client sealing.
  • Presence is not combinable. There is no presence-encrypted- prefix. Cache is: private-encrypted-cache- channels work, and the cached event is stored sealed, though after a key rotation the cached copy stays sealed under the old key until the next publish replaces it.
  • Channel names and event names are not encrypted. Only the payload is. Pick channel names that do not leak what you are protecting.
  • Payloads cannot be inspected server-side. What you gain in confidentiality you give up in inspection: the event log and any payload tooling see ciphertext.

Next steps