Skip to main content

The Sync real-time shared-state model

Sync is Orbit’s real-time shared-state primitive — the answer to “several clients need to see the same changing thing.” A browser session, a mobile app, and a backend worker can all read the same object and observe each other’s writes within milliseconds, without you running any pub/sub infrastructure. The object model mirrors Twilio Sync, so an existing Twilio Sync integration migrates as a path swap. This page explains the model at concept level. For the endpoint-by-endpoint walkthrough, see the Sync real-time state guide; for the raw request/response shapes, the Sync API reference.

1. The four object kinds

Every Sync object is addressed by a unique_name you choose (letters, digits, ., _, :, and -, up to 256 characters) and comes in one of four kinds: The durable kinds persist until you delete them or their ttl expires. A stream persists nothing: only clients subscribed at publish time receive a message. That durability split is the first modeling decision — Sync is built for coordination state, not the system of record. Keep authoritative data in your own database; use Sync for the shared, changing view of it.

2. The convergence loop: REST write → WebSocket broadcast → rehydrate

Sync’s architecture is a write path and a push path that meet at the client:
  1. REST is the only write path. Every mutation — create, update, delete, publish — is a REST call against /api/v1/sync. There is no write-over-socket frame, so a write’s authentication, authorization, and validation always go through the same API pipeline.
  2. A write publishes a change event. The committed write fans an event out to the per-object channel.
  3. The WebSocket gateway relays the event to subscribers. Clients connected to /api/v1/ws/sync and subscribed to that object receive an event envelope (type, kind, unique_name, revision, data, plus key for map events and index for list events). Update events carry the full new data; removal events carry none.
  4. Clients rehydrate over REST after a (re)connect. The gateway is a notification channel, not a state source. After connecting or reconnecting, a client GETs the object’s current state over REST, then applies streamed events on top. The same rehydration repairs anything the client missed while disconnected — the REST read is the convergence point for the durable kinds.
Two consequences follow. First, revision numbers (per object on documents, per item on maps and lists) let a client detect a stale base: if the reply revision moved past the one you held, another writer won, and you converge on a fresh GET instead of retrying with a stale base. Second, a stream never converges — by design. If late-joining clients must see a value, put it in a map or document, not a stream.

One presence map update, end to end

The subscriber converges on the event; the late joiner converges on the REST read. Both end at the same state.

3. Tenant scoping

Tenant scoping is absolute. Every object lives inside exactly one tenant, bound at authentication: the API key or session token you present resolves to a tenant, and every path you address is read inside that tenant’s scope. The WebSocket gateway applies the same binding — subscriptions can only attach to objects in the authenticated tenant. One tenant can never read another tenant’s objects, and there is no addressing form that crosses the boundary. Sync imposes no platform-level gate on what you share inside your tenant. Compliance controls (quiet hours, consent, suppression) are tenant-owned configuration elsewhere in the platform; shape your own usage policy on the object.

4. TTL and expiration semantics

  • ttl is a seconds value (integer, maximum one year) attached to a document or map write. ttl: 0 is the default and means no expiry — the object persists until you DELETE it.
  • Setting a new ttl on a write resets the expiry window from that write. Once the window elapses the object vanishes: reads return SYNC_OBJECT_NOT_FOUND, and subscriptions on it stop receiving updates.
  • Use a short ttl on anything whose absence should clean up automatically — presence entries are the canonical case, because a crashed client then drops off the roster without a cleanup pass. Use ttl: 0 for state you manage explicitly, and delete it when the workflow ends.
  • A stream has no retention at all: a client that was not subscribed at publish time never sees the message, no matter when it connects.

5. The read/write permission split

Sync splits access by operation, tied to organization roles:
  • ReadsGET on any object family, and WebSocket subscriptions — are open to any authenticated member of the organization.
  • Writes — create, update, delete, and stream publish — require an operator role: owner, admin, developer, or agent. The dashboard’s member role can read but not write.
The practical consequence for client applications: anything you embed is a subscriber unless you deliberately make it a writer. Never ship an operator-role API key to end users; let browsers and mobile clients subscribe with scoped session tokens, and keep writes on your backend or on server-minted keys.

6. When to use Sync — and when not to

Orbit has several real-time surfaces. Pick Sync when the shape is shared state, not event delivery:
  • Inbox — a durable alert feed where the item itself is the artifact and items accumulate. Sync objects get overwritten or expire; inbox items accumulate. If the history matters, it is an inbox.
  • Notifications — point-in-time alerts to an operator. If the event is the message, it is a notification, not shared state.
  • Webhooks — server-to-server push to your endpoint. Prefer Sync when the consumers are client applications (browser, mobile, desktop) that you do not want polling; prefer webhooks when the consumer is your backend.
  • Async work — Sync is live coordination and carries no durability guarantee for work. A job that must survive a process restart belongs on the queue backbone, not in a Sync object — see how Orbit processes work asynchronously.
The rule of thumb: if clients converge on a value, it is Sync; if an event must be delivered, it is a webhook or a notification; if items accumulate, it is an inbox; if work must survive a restart, it is a queue.

See also