DocumentationTry Hanzo
Live record updates

Your screen updates
when the data does

Open one stream and records reach the browser as they change — created, updated, deleted — with no polling loop and no timer deciding how stale a page is allowed to get. A subscription is a collection, or a single record inside it, and the access rule already guarding that collection decides what any given subscriber is allowed to receive.

Live connection
One open stream
Records arrive as they change
Client 1Connected
Client 2Connected
Client 3Connected
// Watch a collection
const stop = await base
.collection('messages')
.subscribe('*', (e) => {
console.log(e.action, e.record.id);
render(e.record);
});
// create | update | delete

What the stream gives you

One connection, and the rules you already wrote deciding what goes down it

One stream, server-sent

A single long-lived HTTP response carries every update, so nothing special has to sit in front of it. The browser notices a dropped network and reconnects on its own.

A collection, or one record

Watch a whole collection and hear about everything that lands in it, or watch one record and hear only about that one. Change what you are watching without dropping the stream.

One broker per tenant

An org's subscribers attach to that org's own broker, reading that org's own data. Two tenants using the same collection name are not sharing a topic, so there is no cross-tenant fan-out to get wrong.

Deletes are checked too

A delete is announced only to the subscribers whose rule would have let them read the record. Something you were never allowed to see does not reveal itself on the way out.

The same data, not a copy of it

The stream reads from the store the API reads. There is no separate event bus to keep in step, and nothing that can be current in one place and stale in the other.

The rule is the filter

What reaches a subscriber is decided by the collection's access rule, evaluated against the identity that opened the stream. It is the same predicate that guards the API, not a second one written for the stream.

A grant, not your token

A browser cannot put a header on the request that opens a stream. So you mint a short-lived grant on an ordinary authenticated call and spend it once, within thirty seconds — rather than putting a token that opens every service into a URL that every proxy and access log will keep.

Limits belong to the deployment

How many streams and how many requests are settings on the deployment and counted once for the process, not copied onto each tenant — so a limit of two means two.

Popular Use Cases

Build Interactive Experiences

Hanzo Realtime powers a wide range of applications that demand immediate data updates

Chat Applications

Build messaging apps with real-time delivery, typing indicators, and read receipts.

Collaborative Workspaces

Create shared workspaces where multiple users can collaborate simultaneously.

Collaborative Editing

Several people editing one document, each of them seeing the others' changes land as they happen.

Multiplayer Games

Develop low-latency multiplayer games with synchronized game state across players.

Live Dashboards

Build dashboards that update in real-time as new data becomes available.

Dev Tooling

Create collaborative coding environments with real-time changes and execution.

Three calls, then it is live

Mint a grant, spend it to open the stream, say what you want to hear about

The handshake, in full

There is no hidden protocol here. A grant is minted on a normal authenticated request, the stream spends it once, and the client id that comes back is how you tell the server which collections and records you want. The SDK does all three for you; the raw calls are there if you would rather do it yourself.

1

Mint a grant

One authenticated request returns a string good for thirty seconds and exactly one stream

2

Open the stream, name your topics

Spend the grant, take the client id it hands back, then post the collections and records you care about

3

Write a record

Any create, update or delete reaches everyone watching whose rule lets them see it

The wire, without the SDK
// 1. mint a grant on an ordinary authenticated request
const { token } = await post('/v1/realtime/token');
// 2. spend it — once, and within thirty seconds
const es = new EventSource('/v1/realtime?token=' + token);
es.addEventListener('CONNECT', (ev) => {
const { clientId } = JSON.parse(ev.data);
// 3. name the collections and records you want
post('/v1/realtime', {
clientId,
subscriptions: [
'messages', // the whole collection
'orders/ord_42', // one record
],
});
});
// records arrive as events named for the topic
es.addEventListener('messages', (ev) => {
const { action, record } = JSON.parse(ev.data);
if (action === 'delete') remove(record.id);
else render(record);
});
// a grant is spent; a dropped stream reopens with a new one
es.onerror = () => reconnect();

What happens as it grows

The parts that scale, and the parts that are simply not in the way

A change costs its audience

A record is written once and handed to the subscribers already attached to it. The work is the number of people watching, not the size of the collection they are watching.

Cost of a writePer subscriber

A stream is a response

Not a process, not a socket server beside the API — an HTTP response left open. They are cheap to hold, and one that goes away releases what it held without anything needing to be told.

Per subscriberOne open response

Isolated by tenant

Every org's subscribers sit on that org's own broker, over that org's own data. Adding a tenant adds a broker rather than widening a shared one, so nobody's growth is anybody else's problem.

Blast radiusOne tenant

No polling

The client asks once, not every second

Reconnects itself

A dropped stream reopens with a fresh grant

Nothing extra to run

The same binary that serves the API serves the stream

Open a stream

Two calls and the page keeps itself current

Mint a grant, open the stream, name a collection. No credit card to start.

Documentation

Subscribe to your first collection

Open source

License: Apache-2.0hanzoai

Get Realtime

Realtime channels + presence