# Realtime messages not arriving

> Your channel subscribes but Broadcast or Presence messages never show up. A checklist of the usual causes, from topic and event mismatches to private channels and self-send.

Source: https://supahost.app/kb/realtime-messages-not-arriving · Updated 2026-11-11

## What you see

The channel reports `SUBSCRIBED`, nothing errors, but the handler you registered with `.on(...)` never runs. Or one browser tab receives messages and another doesn't.

```js
channel.subscribe((status) => console.log(status))
// SUBSCRIBED
// ...and then silence
```

If the channel never reaches `SUBSCRIBED`, read [Realtime TIMED_OUT and CHANNEL_ERROR](/kb/realtime-timed-out) instead. If you're listening for database changes, read [Postgres Changes not firing](/kb/realtime-postgres-changes).

## Why it happens

Realtime delivers a message only when the sender and the receiver agree on four things: the topic, the event name, the channel type (public or private), and timing. A mismatch in any of them drops the message silently, because from the server's point of view nobody was listening.

## How to fix it

1. **Compare the topic strings exactly.** `supabase.channel('team-42')` and `supabase.channel('team:42')` are different channels. Log the topic on both sides.
2. **Compare the event names.** The sender's `event` must equal the listener's filter. `'new-message'` and `'new_message'` don't match. Use `{ event: '*' }` while debugging to catch everything.
3. **Register handlers before subscribing.** Chain every `.on()` before `.subscribe()`:

   ```js
   const channel = supabase
     .channel('team-42')
     .on('broadcast', { event: 'new-message' }, ({ payload }) => {
       renderMessage(payload)
     })
     .subscribe()
   ```

4. **Wait for `SUBSCRIBED` before sending.** A `send()` on a socket that hasn't joined yet is queued, but a message sent before the *receiver* has joined is simply missed. Realtime doesn't store Broadcast messages for latecomers.
5. **Remember that senders don't hear themselves.** By default a client doesn't receive its own broadcasts. Testing with one tab looks broken. Open a second tab, or turn on self-send:

   ```js
   supabase.channel('team-42', { config: { broadcast: { self: true } } })
   ```

6. **Match public and private.** A channel created with `config: { private: true }` only receives messages sent to the private topic, and the user needs a `select` policy on `realtime.messages`. A database `realtime.send(...)` call defaults to private. See [Realtime authorization](/docs/realtime/authorization).
7. **Check the token on private channels.** The client must have a signed-in session (or call `supabase.realtime.setAuth(token)` on a server) before it joins.
8. **Look for rate limits.** If you send many messages per second, the server may disconnect the client. See the limits below.

## On SupaHost

Realtime runs on your instance, and each project is its own Realtime tenant with these limits:

| Limit | Value per project |
|---|---|
| Concurrent users | 50,000 |
| Events per second | 5,000 |
| Channel joins per second | 500 |
| Channels per client | 100 |
| Bytes per second | 10,000,000 |

There are no per-message or per-connection fees, so a chatty test doesn't cost extra. The dashboard has no Realtime log viewer yet, so debug from the client side: see [Debugging Realtime with logs and DevTools](/kb/realtime-debugging). Clients connect to `wss://app-one.acme.supahost.space/realtime/v1/...`, which supabase-js builds for you from the project URL.

## Related

- [Broadcast](/docs/realtime/broadcast)
- [Realtime authorization](/docs/realtime/authorization)
- [Debugging Realtime with logs and DevTools](/kb/realtime-debugging)
- [Realtime capacity and limits](/docs/realtime/limits)
