On this page

DataSync events

DataSync events are regular PubNub messages delivered on regular channels. Your app receives them the same way it already does for messaging.

In Bob's marketplace, Bob drops the price on product-sneaker-42, and every shopper watching the sale (including Alice) sees the new price without refreshing or polling. Any create, update, or delete of a DataSync object can publish an event like this.

Enable DataSync events

Events are off by default. You turn them on per class version, per keyset, in the Admin Portal, using event rules. A rule is all-or-nothing: it enables create, update, and delete events together, and there's no way to select individual verbs.

Events are off by default

To receive DataSync events, enable event publishing for the class version in the Admin Portal. With no event rule configured for a class version, no events fire.

Because rules are per class version, publishing a new version of a class stops its events until you add a rule for the new version too.

This applies to entities and relationships you define, and to the built-in users, channels, and memberships. Each is governed by the event rule on its own class version. To see which classes and versions exist on your keyset, list them with Get all entity class entries and Get all relationship classes.

Enable events in the Admin Portal

The event toggle lives on the keyset's DataSync configuration:

  1. In the Admin Portal, go to Apps & Keysets and select your app.
  2. Select the keyset you want to configure.
  3. In the keyset details, scroll to the Configuration section and open the DataSync tab.
  4. Turn on Enable DataSync to stream entity state changes as real-time events.
  5. Under Select entities to sync, choose which classes publish events. Only the classes you select emit events, which is how events stay off by default until you opt a class in. Relationship classes, including the built-in Membership, need rules of their own, so a keyset that enables only entity classes receives no membership events. Each class version is enabled separately.
Enabling a rule isn't retroactive

A rule is read when the write happens, and the result is cached for up to five minutes. Turning a rule on can take that long to affect new writes, and it never applies to writes that already happened. Objects written while events were off don't produce events later.

Region is set once

The DataSync tab is also where you pick the keyset's data Region. Once set, the region can't be changed. Refer to Configuration.

Where events are published

Every entity has an ID channel, named after its own ID, and every event about that entity publishes there.

Entity create, update, and delete events

For an entity:

  • A create event publishes only to the entity's ID channel. A newly created entity has no relationships yet, so there's nowhere else to reach.
  • An update or delete event publishes to the entity's ID channel, plus the ID channel of every other entity it's currently linked to by a relationship.

Relationship events route differently, see Membership events below.

In Bob's marketplace, once seller-bob owns product-sneaker-42 through a ProductOwner relationship, an update to product-sneaker-42 publishes to both the product-sneaker-42 channel and the seller-bob channel.

Membership events

Relationships, including memberships, have no ID channel of their own. Every relationship event, create, update, or delete, publishes to both of its linked entities' ID channels instead.

In Bob's marketplace, an update to product-sneaker-42 arrives on the product-sneaker-42 channel, and on the ID channel of every entity product-sneaker-42 is linked to through a relationship. A membership event arrives on both the linked user's channel and the linked channel's ID channel, since memberships have no channel of their own.

A delete event follows the same routing as any other event. For an entity, it's published to its own ID channel plus the ID channels of its linked entities. For a relationship or membership, it's published to the ID channels of both linked entities.

Deleting an entity also deletes the relationships linked to it, and each of those cascaded deletes publishes an event of its own. Deleting an entity that has three relationships produces one entity-delete event plus three relationship-delete events, so expect a burst rather than a single message.


Projection channels

An event's payload is scoped to the channel it arrives on

The object's own ID channel carries the __default__ view of the payload. Each named projection declared on the class has a channel of its own, __<projection>__<id>, carrying that projection's view. The projections a class declares are listed on its entity class or relationship class definition, per property.

Every change fans out to one event per projection declared on the class, and each copy is filtered to that projection. In Bob's marketplace, where phone belongs only to the admin projection, a subscriber to user-alice never receives phone. A client that needs phone in real time subscribes to __admin__user-alice instead.

status is scoped the same way payload is. A class that declares no /status property treats it as __default__ only, so it drops out of named-projection channels. Declare /status tagged with a projection (see Reading objects) to include it there too. The other system fields, id, eTag, createdAt, updatedAt, and expiresAt, are identical on every channel. A delete event has no payload or status, so the same { id, deletedAt } body reaches every one of those channels. If a class declares no properties at all, a single unfiltered event is published to the object's own ID channel.

Publishing to a projection channel is an ordinary channel publish, and a token's projection entries don't gate it. Use Access Manager channel grants to control who can subscribe to a projection channel. See Projections and events.

Subscribe to what you display

To stay current on one entity, subscribe to its ID channel. To follow a set of entities, subscribe to each of their ID channels.

A relationship or membership has no ID channel of its own, so to follow one, subscribe to the channels of the two entities it links.

If the class declares named projections and you need one of those views, subscribe to __<projection>__<id> as well. In an SDK that ships DataSync SDK entities, pass the projection name as a subscription option instead of writing the prefixed channel name yourself.

Event format

A DataSync event is a PubNub message with message type 5. This distinguishes it from an ordinary message. For more information on message types, see Message types. The envelope shape is:

{
"version": "1.0",
"metadata": {
"event": "create" | "update" | "delete",
"source": "data-sync",
"type": "user" | "channel" | "membership" | "entity" | "relationship",
"className": "Product",
"classLevel": "Global" | "SubKey",
"classVersion": 1
},
"data": { ... }
}

For create and update, data carries the object's current state, its system fields plus its payload as scoped for that channel's projection. The object's class and class version aren't repeated in data, they're in metadata.

For delete, data is exactly { id, deletedAt }, for every object kind alike. Relationship create and update events additionally carry entityAId and entityBId, and membership create and update events carry channelId and userId in their place. Delete events carry neither pair, so track the sides from the earlier create event if you need them.

note
metadata.type names the object kind directly

Each of the five object kinds has its own type value: user, channel, membership, entity, and relationship. A user reports user rather than entity, and a membership reports membership rather than relationship, so you can dispatch on type alone without matching class names.

The value is derived from the Global class your class descends from, not from its name, so a class of your own that extends the built-in User reports type: "user" alongside its own className and classLevel: "SubKey", and a class of your own merely named User that extends nothing reports type: "entity".

Fields that have no value are left out of data rather than sent as null, so status and payload are absent on an object that has neither. Read every field except id defensively.

Events from Bob's marketplace look like this:

{
"version": "1.0",
"metadata": {
"event": "update",
"source": "data-sync",
"type": "entity",
"className": "Product",
"classLevel": "SubKey",
"classVersion": 1
},
"data": {
"id": "product-sneaker-42",
"status": "active",
"eTag": "a1b2c3",
"createdAt": "2026-06-01T10:00:00Z",
show all 20 lines
FieldDescription
version
Envelope version, 1.0 for DataSync events
metadata.event
create, update, or delete
metadata.source
Always data-sync
metadata.type
The object kind: user, channel, membership, entity, or relationship
metadata.className
The class name, for example Product or User. Read it together with classLevel to tell a built-in class from one of your own that happens to share its name
metadata.classLevel
The class's level, Global or SubKey
metadata.classVersion
The class version the object pins to
data
On create and update: id, createdAt, updatedAt, expiresAt, eTag, status, and payload, plus entityAId and entityBId for relationships, or channelId and userId for memberships. On delete: exactly { id, deletedAt }, for every object kind alike

className is the plain class name. The class's level travels in a separate field, classLevel. A class you define on your keyset reports classLevel: "SubKey", and a built-in class like User reports classLevel: "Global".

Reading the same update event shown above:

1subscription.onDataSync = (event) => {
2 const change = event.message
3 console.log(change.event, change.className, change.data.payload?.price)
4}

The JS SDK flattens the wire envelope: metadata.event arrives as event.message.event, metadata.className as event.message.className, and so on. event.message.data is the raw data object shown above.

Refer to Add DataSync listener (JavaScript) and Add DataSync listener (C#) for the full field reference of the event object each SDK exposes.

Receive DataSync events

Subscribe to the channels an object's events reach and handle messages with message type 5, the same way you handle any other message on a channel. Refer to Where events are published for which channels those are.

A common pattern is to fetch the object once to get its current state, then apply incoming events to keep that state current. An entity's channel can also receive relationship events and events about connected entities, so first check the event's object type and data.id and ignore changes for anything other than the object you're tracking. Compare eTag or updatedAt on create and update events against what you have to detect whether you missed an update and to discard duplicates. Delivery is at-least-once, so the same event can arrive more than once. Delete events carry only id and deletedAt, so use those two fields when deduplicating deletes.

Events for the same object arrive in order. Events for different objects can interleave, and there's no ordering guarantee across objects or across channels.

Subscribe with a DataSync SDK entity

SDKs that support DataSync give you a DataSync SDK entity per object kind, so you subscribe by object identifier instead of building the channel name yourself. The handle takes the identifier verbatim and resolves it to a channel:

You createIt observes
A user, channel, or entity handle for id
The id channel
The same handle with a projection of admin
The __admin__id channel

Passing no projection, or default or __default__, observes the object's own ID channel. Any other name observes that projection's channel. The projection is chosen per subscription rather than per handle, so one handle can serve several projections at once, and each one subscribes and unsubscribes on its own. Both subscriptions receive their own copy of a change, with the projected one carrying whatever extra fields that projection exposes.

There's no presence channel for a DataSync object, so a receivePresenceEvents option has no effect on these handles.

A relationship or membership handle receives nothing for the link itself

Relationship and membership events never reach a channel named after the relationship or membership id, so subscribe to the two linked entities instead. Refer to Membership events.

For SDK-specific method names and examples, refer to JavaScript, which provides DataSync SDK entities, or C#, where you subscribe to the object's data channel with a Channel SDK entity and attach a listener to the resulting Subscription, a SubscriptionSet, or the PubNub client. These are the only two SDKs with DataSync support, refer to SDK support.

Follow a group of objects with a wildcard

Because a handle uses the identifier verbatim, a dot-namespaced identifier ending in .* subscribes to a channel pattern and one subscription covers every matching object. Subscribing to product.* delivers changes for product.sneaker-42 and product.cap-7 alike, and __admin__product.* does the same for the admin projection. This is ordinary Wildcard Subscribe.

An event delivered through a wildcard reports the concrete delivery channel rather than the pattern. Because fan-out can deliver a change on a connected object's channel, read the changed object's id from data rather than inferring it from the channel.

1const subscription = pubnub.dataSyncEntity('product.*').subscription()
2
3subscription.onDataSync = (event) => {
4 // event.channel -> 'product.sneaker-42'
5 // event.subscription -> 'product.*'
6 console.log('changed:', event.message.data.id)
7}
8
9subscription.subscribe()

Refer to DataSync SDK entities (JavaScript) and Add DataSync listener (C#) for the full method reference.

icon

How DataSync events compare to App Context events