tutorial··5 min read·WoWSQL Team

Postgres Realtime Subscriptions over WebSockets, Explained

Postgres realtime subscriptions, explained: LISTEN/NOTIFY vs logical replication, and how to stream INSERT, UPDATE and DELETE events to apps with WoWSQL.

Postgres realtime subscriptions let your app hear about database changes the moment they happen, instead of asking the database every few seconds whether anything is new. A chat screen shows the new message, a dashboard updates its numbers, and an order page flips from "pending" to "paid" without a refresh. Postgres itself doesn't push data to browsers, so something has to sit between the database and your users: a service that notices changes and sends them over a WebSocket.

This guide explains how that works, the two building blocks Postgres gives you, and how to subscribe to table changes, broadcast messages and presence with WoWSQL's TypeScript SDK.

Key facts

  • Postgres has no built-in push to browsers. It offers LISTEN/NOTIFY and logical replication; a realtime service turns those into WebSocket messages.
  • WoWSQL realtime endpoint: wss://<slug>.wowsqlconnect.com/realtime/v1/websocket?apikey=<key>, using the same anon or service key as the REST API.
  • Three features (docs checked 26 October 2026): Postgres change events (INSERT, UPDATE, DELETE), broadcast messages and presence.
  • Tables must be enabled first with POST /realtime/v1/enable. Broadcast and presence need no table at all. Payloads are capped at 64 KiB.
  • SDK: @wowsql/sdk 3.10.0 exposes client.realtime on new WowSQLClient({ projectUrl, apiKey }), next to .table(...).
  • Plans: realtime is listed on Free, Pro ($25 a month) and Scale ($99 a month). Regions are Mumbai and US East 1.

Polling vs realtime subscriptions

The simplest way to show fresh data is polling: call client.table('messages').select('*').get() every five seconds. It works, and for an admin screen that a few people open, it's often enough. It gets expensive and laggy as users grow. Every open tab sends requests even when nothing changed, and a change can still take up to the full interval to appear.

A subscription flips this around. The client opens one WebSocket and keeps it open. When a row changes, the server sends a small message, and the client updates its state. You get lower latency and far fewer wasted requests, at the cost of managing long-lived connections.

How Postgres change data reaches a WebSocket

Postgres offers two mechanisms that realtime services build on.

LISTEN/NOTIFY. Any session can run LISTEN orders_channel, and any transaction can run NOTIFY orders_channel, 'payload'. A trigger on a table can call pg_notify after each insert or update. Notifications are delivered when the transaction commits, so listeners never see rolled-back changes. The limits matter: in the default configuration a payload must be shorter than 8,000 bytes, according to the PostgreSQL docs, and a listener that is disconnected when a notification fires simply misses it. The usual pattern is to send the row's ID and let the client fetch the rest.

Logical replication. Postgres can decode its write-ahead log into a stream of row changes, which is how change data capture (CDC) tools work. It captures every committed change without triggers, but it needs replication slots, careful monitoring of slot lag and more setup.

Either way, a separate service holds the database connection, turns changes into JSON and fans them out to WebSocket clients. WoWSQL's self-hosted edition documents its realtime service as using LISTEN/NOTIFY. On the hosted platform you don't run any of this yourself: you enable a table and subscribe.

Step 1: Enable realtime on a table

Create a project at app.wowsql.com in Mumbai or US East 1, and create a table, for example:

create table public.messages (
  id uuid primary key default gen_random_uuid(),
  room text not null,
  body text not null,
  created_at timestamptz not null default now()
);

Postgres change events only flow for tables you've enabled. Call the enable endpoint once per table. We suggest doing it from a setup script or server, not from app code:

curl -X POST "https://myproject.wowsqlconnect.com/realtime/v1/enable" \
  -H "apikey: $WOWSQL_SERVICE_KEY" \
  -H "Content-Type: application/json" \
  -d '{"schema_name":"public","table_name":"messages"}'

If you're new to the project's REST layer, our instant REST API for Postgres guide covers keys and endpoints.

Step 2: Subscribe to INSERT, UPDATE and DELETE

import WowSQLClient from '@wowsql/sdk';

const client = new WowSQLClient({
  projectUrl: 'myproject',             // https://myproject.wowsqlconnect.com
  apiKey: 'wowsql_anon_your_key_here', // anon key in browsers and apps
});

const channel = client.realtime.channel('room-general');

channel
  .on('postgres_changes', { event: 'INSERT', schema: 'public', table: 'messages' }, (change) => {
    const row = change.new as { room: string; body: string };
    if (row.room === 'general') renderMessage(row);
  })
  .subscribe();

Each change carries event (INSERT, UPDATE or DELETE), schema, table, and new or old with the row data. Use event: '*' to receive all three. If you only want table changes without a named channel, client.realtime.subscribe({ table: 'messages', event: '*' }, cb) returns a subscription with an unsubscribe() method.

In SDK 3.10.0 the subscription filter takes a schema, table and event, but not a row filter. In the example we check row.room on the client. For busy tables, split data into separate tables or filter on a server you control.

Step 3: Add broadcast and presence

Not every realtime feature needs a database row. Typing indicators and cursor positions are short-lived, so writing them to Postgres would be wasteful. Broadcast sends a message straight to everyone on the channel:

channel.on('broadcast', { event: 'typing' }, ({ payload }) => showTyping(payload));
await channel.send({ type: 'broadcast', event: 'typing', payload: { user: 'Asha' } });

Presence tracks who is connected:

const room = client.realtime.channel('online-users');
room.on('presence', { event: 'sync' }, () => {
  console.log('Online now:', Object.keys(room.presenceState()).length);
});
room.subscribe(async (status) => {
  if (status === 'SUBSCRIBED') await room.track({ user_id: currentUser.id });
});

Call room.untrack() when a user leaves and channel.unsubscribe() when the screen unmounts. In React, put the subscribe call in a useEffect and unsubscribe in its cleanup. Our Next.js Postgres backend guide shows where client components fit, and the React Native guide covers mobile setup.

Connections, errors and security

Browsers can't set custom headers on a WebSocket, so the key always goes in the apikey query parameter. A missing key closes the socket with code 4001 and an invalid key with 4003. The SDK reconnects with exponential backoff on other drops, but don't retry on 4003. Fix the key instead.

Treat channels as shared rooms. The SDK notes that anyone who can connect may listen and send on channels they've joined, so don't broadcast private data on a guessable channel name, and keep the service key out of client code. For per-user data, have a server subscribe with the service key, check who should see each change, and forward only that. Our Postgres authentication guide explains how to verify user tokens on that server.

How this compares with Supabase Realtime

Supabase's docs (checked 26 October 2026) describe the same three features: Broadcast, Presence and Postgres Changes. The ideas transfer, but the client APIs are different, so plan to rewrite subscription code rather than copy it. WoWSQL is built in India, DPIIT-recognised and part of NVIDIA Inception, with Mumbai as the default region.

FAQ

Does Postgres support realtime subscriptions natively?

Not to browsers. Postgres provides LISTEN/NOTIFY and logical replication, which let a server learn about changes. You need a realtime service, like WoWSQL's, to push those changes to clients over WebSockets.

Should I use LISTEN/NOTIFY or logical replication?

LISTEN/NOTIFY is simple and fine for small payloads and short outages, but missed notifications aren't replayed. Logical replication captures every committed change and suits CDC pipelines, at the cost of managing replication slots.

Do I need to enable each table for realtime in WoWSQL?

Yes, for Postgres change events. Call POST /realtime/v1/enable with the schema and table name. Broadcast and presence work on any channel name without enabling a table.

Is realtime included on WoWSQL's free plan?

Yes. The pricing page lists realtime on the Free plan, and on Pro at $25 a month and Scale at $99 a month.

Can I filter realtime events to specific rows?

SDK 3.10.0 filters by schema, table and event type. To narrow it to specific rows, check fields in your callback, split data across tables, or filter in a server that forwards changes to users.