- WebSocket:
wss://{ref}.p.powabase.ai/realtime/v1/websocketfor subscriptions. - REST:
https://{ref}.p.powabase.ai/realtime/v1/apifor server-side broadcast.
Authentication
Realtime authenticates the WebSocket at upgrade time via query parameters. There are no headers on the WS handshake because most browser WebSocket APIs don’t let you set them.
The REST endpoint uses standard headers (
apikey plus Authorization: Bearer <token>) and requires the Service Role Key, not the Anon Key.
Frame format
All WebSocket frames are Phoenix Channels JSON envelopes:topic: the channel name. Convention isrealtime:<schema>:<table>for postgres_changes channels, or any arbitrary string for broadcast/presence.event: what kind of frame (see below).payload: event-specific data.ref: client-chosen ID that the server echoes back onphx_reply. Useful for correlating sends with their acknowledgments.
WebSocket events
phx_join
Client → server. Subscribes to a channel.config.broadcast:
self(bool, defaultfalse): receive your own broadcast messages.ack(bool, defaultfalse): receivephx_replyconfirming the broadcast was routed.
config.presence:
key(string): unique identifier for this presence (typically the user id). Used for dedupe across multiple connections.
config.postgres_changes, array of filter specs:
event(string):INSERT,UPDATE,DELETE, or*for all three.schema(string): target schema.publicis the common case.table(string): target table.filter(string, optional): column equality in PostgREST-style syntax (e.g.,user_id=eq.<uuid>,status=eq.active).
config.private (bool, default false): when true, Realtime checks the SELECT policy on realtime.messages before letting you join. Without a passing policy, the join returns an error.
phx_reply
Server → client. Acknowledgment for aphx_join, broadcast, or other client-initiated frame.
ref matches the client’s send. On error, response includes { reason: "<machine-readable>" }.
postgres_changes
Server → client. A row change matching a previously-subscribed postgres_changes config.UPDATE, both new and old are populated. For DELETE, only old is. The columns the client sees depend on the table’s REPLICA IDENTITY: DEFAULT shows changed columns plus the primary key on UPDATE; FULL shows every column. To set FULL:
broadcast
Client → server, and server → client. Custom messages on the channel. Client send:event field inside payload is your custom event type (e.g., chat_message, typing, cursor_move). The outer event is always broadcast.
presence_state
Server → client. Initial snapshot of who’s currently tracked in the channel, sent right after a successfulphx_join with presence config.
key values from each presence, typically user ids. The values are arrays because the same key can be tracked from multiple connections (e.g., two browser tabs by the same user).
presence_diff
Server → client. Incremental changes to the presence state after the initialpresence_state.
action is track or untrack. data is what other subscribers see in presence_state.
system
Server → client. Notifications about the channel itself (subscribed, unsubscribed, errors).system frame with status: "error" here usually means the publication isn’t set up. See Realtime model.
phoenix heartbeat
Client → server. The Phoenix Channels convention is a heartbeat every 30 seconds; if no heartbeat arrives for roughly two intervals (the Phoenix default, which Powabase doesn’t override), Realtime closes the connection. Most clients send heartbeats automatically.phx_reply of { status: "ok" }. If you don’t get an OK within a few seconds, the connection is probably half-open; disconnect and reconnect.
phx_leave
Client → server. Cleanly leave a channel without closing the WebSocket.phx_close
Server → client. Channel was closed (by you, by an error, or by the server). After this, send a newphx_join to re-subscribe.
REST: broadcast
For server-side message emission. Use the Service Role Key.POST /realtime/v1/api/broadcast
Send one or more broadcast messages to channels.array
required
Array of message objects.
string
required
Channel name to broadcast on.
string
required
Your custom event type (matches the inner
event field clients see).object
required
Your message data.
boolean
Default
false. When true, only clients subscribed to a private version of the channel receive the message.{ "message": "ok" } on success. The endpoint is fire-and-forget: it doesn’t tell you how many subscribers received the message.
SQL: realtime.send() and realtime.broadcast_changes()
The Realtime image installs two functions in therealtime schema that let your Postgres triggers emit broadcast messages directly:
realtime.send(payload jsonb, event text, topic text, private boolean default false)
Send a single broadcast message from SQL. Equivalent to a single-message POST to/realtime/v1/api/broadcast.
realtime.broadcast_changes(topic text, event text, op text, table text, schema text, new record, old record, level text default ‘topic’)
Higher-level wrapper for “broadcast this row change.” Designed to be called from a trigger function (see Realtime model for a worked trigger example). Thelevel parameter is the channel-privacy level:
'topic'(default): sends to a private channel; subscribers need a passing RLS policy onrealtime.messagesto receive.- Any other string: treated as public.
Channel-private auth: realtime.messages
Private channels gate join with a SELECT policy onrealtime.messages. The function realtime.topic() returns the channel name being checked, so the policy can condition the decision on the topic:
Error responses
WebSocket errors arrive asphx_reply frames with payload.status: "error":
WebSocket connection errors:
REST errors: standard JSON shape,
{ "error": "<message>" }.
Service versions and notes
- Realtime:
v2.65.3 - Both routes (
/realtime/v1/and/realtime/v1/api) use Kong’spreserve_host: trueto let Realtime parsetenant_idfrom the Host header. - Per-project Realtime pods are seeded with
SEED_SELF_HOST=true, so the tenant is created on first connect rather than requiring an out-of-band provisioning step. - The Realtime
DB_ENC_KEYis shared across Powabase deployments. It’s an internal multi-tenant encryption key, not a per-project secret, and isn’t user-relevant.
Next steps
Realtime model
The conceptual underpinning: three channels, auth, the publication gotcha.
Realtime subscriptions
Three worked patterns in TypeScript with the protocol details from this page applied.
RLS Cookbook
The policies that gate private channels and the underlying tables for Postgres Changes.
Auth Reference
Where the access tokens that authenticate Realtime connections come from.