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.
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.
channel.subscribe((status) => console.log(status))
// SUBSCRIBED
// ...and then silence
If the channel never reaches SUBSCRIBED, read Realtime TIMED_OUT and CHANNEL_ERROR instead. If you're listening for database changes, read Postgres Changes not firing.
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
Compare the topic strings exactly.
supabase.channel('team-42')andsupabase.channel('team:42')are different channels. Log the topic on both sides.Compare the event names. The sender's
eventmust equal the listener's filter.'new-message'and'new_message'don't match. Use{ event: '*' }while debugging to catch everything.Register handlers before subscribing. Chain every
.on()before.subscribe():const channel = supabase .channel('team-42') .on('broadcast', { event: 'new-message' }, ({ payload }) => { renderMessage(payload) }) .subscribe()Wait for
SUBSCRIBEDbefore sending. Asend()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.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:
supabase.channel('team-42', { config: { broadcast: { self: true } } })Match public and private. A channel created with
config: { private: true }only receives messages sent to the private topic, and the user needs aselectpolicy onrealtime.messages. A databaserealtime.send(...)call defaults to private. See Realtime authorization.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.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. Clients connect to wss://app-one.acme.supahost.space/realtime/v1/..., which supabase-js builds for you from the project URL.