Documentation
Sign inGet started

Realtime webhooks

Publishing sends events out to clients. Webhooks run the other direction: the Realtime edge POSTs a signed event to your endpoint when something happens on a channel.
You can already read channel state on demand with Querying channel state. Webhooks are how you hear about a change as it happens, without polling: a client subscribing, a member closing their last tab, one client sending another a cursor position.

The five event groups

Subscribe to one or more event groups:
GroupThe question it answersWhat it delivers
realtime.channel_existenceIs anyone listening?realtime.channel_occupied, realtime.channel_vacated
realtime.presenceWho is here?realtime.member_added, realtime.member_removed
realtime.connection_countHow many connections?realtime.connection_count
realtime.cache_channelsDoes this channel need data?realtime.cache_miss
realtime.client_eventsWhat are clients sending?one event per client event, named after it
channel_existence reports only the two edges of a channel's life: channel_occupied when it goes from zero connections to one, channel_vacated when its last connection leaves. Subscribers arriving and leaving in between produce nothing, which makes it the cheap way to know whether publishing is worth it.
connection_count requires connection counting on the app. If the setting is disabled, the subscribed group produces no events.

Subscribe an endpoint

Open Webhooks, create or edit an endpoint, and find the Realtime events section. Select one Realtime app and the groups to receive.
Platform event subscriptions apply across the workspace, while realtime.* subscriptions belong to one app. You cannot change the endpoint's Realtime app after creating it, but you can update its subscribed groups.
Platform and Realtime events can share an endpoint. For example, one endpoint can subscribe to email.bounced and realtime.presence. Both use the endpoint's signing secret.
Realtime groups are currently dashboard-only. A public POST /v1/webhooks request that includes a realtime.* event type is rejected, so configure these subscriptions in the dashboard.

What a delivery looks like

Each POST contains one event in Bird's standard webhook envelope:
代码示例
{
  "data": { "channel": "presence-room-1", "member_id": "u_42" },
  "timestamp": "2026-07-31T09:00:00Z",
  "type": "realtime.member_added"
}
type identifies the delivered event. For example, the realtime.presence group delivers realtime.member_added and realtime.member_removed:
Delivered typedata fields
realtime.channel_occupiedchannel
realtime.channel_vacatedchannel
realtime.member_addedchannel, member_id
realtime.member_removedchannel, member_id
realtime.connection_countchannel, connection_count
realtime.cache_misschannel
realtime.<client event>channel_name, event, data, connection_id, plus member_id on a presence channel
For client events, the client chooses the event suffix. Triggering client-typing produces realtime.client-typing, and data.event contains client-typing. See Client events.

Verifying a delivery

Realtime webhooks follow Standard Webhooks and include webhook-id, webhook-timestamp, and webhook-signature headers. Verify them with the endpoint's signing secret.
代码示例
import { BirdClient } from "@messagebird/sdk";

const bird = new BirdClient({
  apiKey: process.env.BIRD_API_KEY,
  webhooks: { secret: process.env.BIRD_WEBHOOK_SECRET },
});

app.post("/webhooks/bird", express.raw({ type: "*/*" }), (req, res) => {
  const event = bird.webhooks.unwrap(req.body, req.headers);
  res.sendStatus(200);

  switch (event.type) {
    case "realtime.channel_vacated":
      stopExpensiveWorkFor(event.data.channel);
      break;
    case "realtime.member_removed":
      markAway(event.data.member_id);
      break;
  }
});
Verify the raw request body because parsing and re-serializing JSON can change the signed bytes. Handle unknown event types in a default branch so new types do not break the endpoint. See Verify webhook signatures for the complete contract.

Presence events count members

member_added and member_removed follow the identity, so they do not line up one-to-one with connections. Somebody with your app open in three tabs is one member:
What happensWebhook
First tab subscribesrealtime.member_added
Second tab subscribesnone
Second tab closesnone
Last tab closesrealtime.member_removed
Use member_removed to detect when an identity leaves a channel. A single session ending does not trigger it while another session remains. To track connections, subscribe to realtime.connection_count. See Presence channels.

Realtime webhook delivery behavior

Realtime deliveries use these retry and visibility rules:
  • Return 2xx promptly after durably accepting the event, then process it asynchronously.
  • If your endpoint returns a non-2xx response, Realtime retries with exponential backoff for up to 5 minutes.
  • Realtime events have no replay and do not appear in the endpoint's delivery-attempts log.
  • Pausing the endpoint stops Realtime deliveries along with everything else, and re-enabling it resumes them.
Deliveries are unordered and provide no per-event receipt. Treat them as change notifications. Use Querying channel state to retrieve current state after delayed or missing events.

Next steps

  • Client events is the group whose event names and payloads you define yourself.
  • Cache channels explains what to do with realtime.cache_miss.
  • Webhooks & events covers endpoint setup, signature verification, and secret rotation for every Bird webhook.