Both push. That is the only thing they have in common, and the differences are structural rather than a matter of degree.

A **webhook** delivers one event to one HTTPS endpoint that you own and operate. Bird makes the request, your server answers, and the delivery is retried if it fails.

A **pub/sub realtime API** works the other way round: your server publishes an event to a named channel, and every client currently subscribed to that channel receives it over its own WebSocket. There is no endpoint of yours involved and nothing is retried.

## What are channels, members and connections?

Three words that are not interchangeable, and getting them straight saves a lot of confusion later.

A **channel** is a named room. It exists while at least one connection is subscribed and disappears when the last one leaves, so you do not create or delete channels; you publish to a name and it exists if anyone is listening.

A **connection** is one open WebSocket. It receives an id when it connects, and that id is what authorisation signs and what a publish can exclude, which is how you avoid echoing an event back to the client that caused it.

A **member** is an authenticated identity on a presence channel. One member can hold several connections, three browser tabs being the obvious case, and **presence events fire only when the member's first connection joins and their last one leaves.** The tabs in between produce nothing, which is what you want and not what a naive implementation would do.

## Who is allowed to subscribe?

The channel name decides, which is the part with real security consequences.

The prefix selects the type. A **bare name** is public: readable by anyone holding the app key, and the app key ships inside client code, so publish only what every visitor may see. A **`private-` prefix** asks your backend to approve each subscription: the client posts the connection id and channel name to your endpoint, and you return a signature computed with the app secret. A **`presence-` prefix** does the same and adds an identity, so every subscriber receives the member list and its changes.

There is also an encrypted variant of a private channel, where payloads are encrypted with a key your servers hold.

The rule underneath is worth stating plainly: **the app key is public by construction** and the app secret is what stays on your server. Anything that must not be readable by a visitor goes on a private or presence channel, not on a public one with an obscure name.

## What does the difference mean in practice?

Three consequences, and the first is the one that decides most designs.

**Delivery guarantees.** A webhook that fails is retried for about a day and can be replayed after that. A pub/sub event published while a client is disconnected is simply missed; the client reconnects and receives what happens next. A cache channel softens that by replaying its latest event to each new subscriber, but it keeps no history, so a client that missed two updates gets the current state and never the gap.

**Who you are talking to.** A webhook is server-to-server, which means it can carry things a browser should never see. A channel's subscribers are clients you do not control, on machines you do not own.

**What arrives.** Webhook events come from Bird's own catalogue, with types you subscribe to. Channel events are yours: you choose the name at publish time and bind a handler to it. Alongside those, the client re-emits lifecycle events under a reserved prefix for subscription success, member changes and connection counts.

## Which should I use?

Usually both, for different jobs, and the choice is covered properly in [webhooks, polling or streaming](/explained/platform/should-i-use-webhooks-polling-or-streaming).

The short version: if your system must react and must not lose the event, that is a webhook. If a person is looking at a screen that should change, that is a channel. An order status page often wants both, a webhook so your database is right and a publish so the page moves without a refresh.

One asymmetry to know before you plan around it: Realtime has webhooks of its own, telling your server when a channel becomes occupied or vacated and when members come and go. Those are configured in the dashboard rather than through the public webhooks API.

[Realtime overview](/docs/guides/realtime/overview) has the channel model and the client libraries, and [webhooks](/docs/guides/webhooks) has the delivery semantics on the other side.