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

  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():

    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:

    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.

  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. Clients connect to wss://app-one.acme.supahost.space/realtime/v1/..., which supabase-js builds for you from the project URL.

Updated 11 November 2026 · View as Markdown · Something wrong or missing? Tell us