---
source_url: https://www.pubnub.com/docs/general/data-sync/events
title: DataSync events
updated_at: 2026-09-08T16:52:30.000Z
---

# DataSync events

## Documentation index

To discover more PubNub resources:

1. Fetch [PubNub's llms.txt](https://www.pubnub.com/llms-full.txt) for a list of available pages in Markdown format.
2. Identify relevant URLs from that index.
3. Fetch the target pages.

Do not assume a path exists, always check the index first.

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](https://www.pubnub.com/docs/general/data-sync/overview.md#running-example-bobs-live-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.

###### Events arrive through ordinary subscribe

DataSync events are messages on ordinary channels. See [Receive messages](https://www.pubnub.com/docs/general/messages/receive.md) for how subscribing and listening works.

## Enable DataSync events

Events are off by default. You turn them on per class version, per keyset, in the [Admin Portal](https://admin.pubnub.com), 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.

:::note 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](https://www.pubnub.com/docs/admin-api/get-all-entity-class-entries.md) and [Get all relationship classes](https://www.pubnub.com/docs/admin-api/get-all-relationship-classes.md).

### Enable events in the Admin Portal

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

1. In the [Admin Portal](https://admin.pubnub.com), 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.

:::note 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.
:::

:::note 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](https://www.pubnub.com/docs/general/data-sync/overview.md#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](#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

:::warning 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](https://www.pubnub.com/docs/admin-api/get-entity-class-by-id.md) or [relationship class](https://www.pubnub.com/docs/admin-api/get-relationship-class-by-name-and-version.md) 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](https://www.pubnub.com/docs/general/data-sync/data-operations.md#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](https://www.pubnub.com/docs/general/data-sync/projections.md#projections-and-events).

:::tip 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](https://www.pubnub.com/docs/general/entities.md#datasync-sdk-entities), pass the projection name as a subscription option instead of writing the prefixed channel name yourself.
:::

###### Access Manager controls who can subscribe

Channel-level Access Manager grants determine who can subscribe to an object's channel and receive its events. See [Access control and permissions management](https://www.pubnub.com/docs/general/security/access-control.md).

## 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](https://www.pubnub.com/docs/general/messages/type.md). The envelope shape is:

```json
{
  "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:

###### Update event

```json
{
  "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",
    "updatedAt": "2026-07-03T09:15:00Z",
    "expiresAt": "2026-08-01T00:00:00Z",
    "payload": { "name": "Retro Sneaker", "price": 79.99, "stock": 12 }
  }
}
```

###### Delete event

```json
{
  "version": "1.0",
  "metadata": {
    "event": "delete",
    "source": "data-sync",
    "type": "entity",
    "className": "Product",
    "classLevel": "SubKey",
    "classVersion": 1
  },
  "data": {
    "id": "product-sneaker-42",
    "deletedAt": "2026-07-03T09:20:00Z"
  }
}
```

###### Membership create event

A membership event carries `type: "membership"` and names its sides `channelId` and `userId`, matching the field names the [membership REST resource](https://www.pubnub.com/docs/general/data-sync/users-channels-memberships.md) uses:

```json
{
  "version": "1.0",
  "metadata": {
    "event": "create",
    "source": "data-sync",
    "type": "membership",
    "className": "Membership",
    "classLevel": "Global",
    "classVersion": 1
  },
  "data": {
    "id": "membership-alice-sale",
    "status": "active",
    "eTag": "d4e5f6",
    "createdAt": "2026-07-03T09:15:00Z",
    "updatedAt": "2026-07-03T09:15:00Z",
    "expiresAt": "2026-08-01T00:00:00Z",
    "channelId": "channel-summer-sale",
    "userId": "user-alice",
    "payload": { "role": "viewer" }
  }
}
```

A membership event never carries `entityAId` or `entityBId`. A relationship of your own carries those two instead and never `channelId` or `userId`.

| Field | Description |
| --- | --- |
| `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:

###### JavaScript

```javascript
subscription.onDataSync = (event) => {
    const change = event.message
    console.log(change.event, change.className, change.data.payload?.price)
}
```

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.

###### C#

```csharp
var listener = new SubscribeCallbackExt(
    (Pubnub pn, PNDataSyncEventResult dataSyncEvent) =>
    {
        Console.WriteLine(dataSyncEvent.Event);
        Console.WriteLine(dataSyncEvent.ClassName);
        Console.WriteLine(dataSyncEvent.EntityData?.Payload["price"]);
    },
    (Pubnub pn, PNStatus status) => { /* handle status */ });
```

The C# SDK exposes the same fields as properties on `PNDataSyncEventResult`: `Event`, `ClassName`, `ClassLevel`, `ClassVersion`, and `Source`, with the object state on `EntityData` (or `RelationshipData` for memberships and relationships).

###### curl

```bash
curl -N 'https://ps.pndsn.com/v2/subscribe/{subKey}/product-sneaker-42/0?tt=0&auth=<token>'
```

```json
[
    [
        {
            "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",
                "updatedAt": "2026-07-03T09:15:00Z",
                "expiresAt": "2026-08-01T00:00:00Z",
                "payload": { "name": "Retro Sneaker", "price": 79.99, "stock": 12 }
            }
        }
    ],
    "15467028383205111"
]
```

Refer to [Add DataSync listener (JavaScript)](https://www.pubnub.com/docs/sdks/javascript/api-reference/publish-and-subscribe.md#add-datasync-listener) and [Add DataSync listener (C#)](https://www.pubnub.com/docs/sdks/c-sharp/api-reference/publish-and-subscribe.md#add-datasync-listener) for the full field reference of the event object each SDK exposes.

## Receive DataSync events

[Subscribe](https://www.pubnub.com/docs/general/channels/subscribe.md) 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](#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](https://www.pubnub.com/docs/general/entities.md#datasync-sdk-entities) 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 create | It 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.

:::warning 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](#membership-events).
:::

For SDK-specific method names and examples, refer to [JavaScript](https://www.pubnub.com/docs/sdks/javascript/api-reference/publish-and-subscribe.md#datasync-sdk-entities), which provides DataSync SDK entities, or [C#](https://www.pubnub.com/docs/sdks/c-sharp/api-reference/publish-and-subscribe.md#add-datasync-listener), 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](https://www.pubnub.com/docs/general/data-sync/overview.md#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](https://www.pubnub.com/docs/general/channels/subscribe.md#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.

#### JavaScript

```javascript
const subscription = pubnub.dataSyncEntity('product.*').subscription()

subscription.onDataSync = (event) => {
    // event.channel      -> 'product.sneaker-42'
    // event.subscription -> 'product.*'
    console.log('changed:', event.message.data.id)
}

subscription.subscribe()
```

#### C#

```csharp
Channel channel = pubnub.Channel("product.*");
Subscription subscription = channel.Subscription();

var listener = new SubscribeCallbackExt(
    (Pubnub pn, PNDataSyncEventResult dataSyncEvent) =>
    {
        string changedId = dataSyncEvent.EntityData?.Id
            ?? dataSyncEvent.RelationshipData?.Id
            ?? dataSyncEvent.Id;
        Console.WriteLine("changed: " + changedId);
    },
    (Pubnub pn, PNStatus status) => { /* handle status */ });

pubnub.AddListener(listener);
subscription.Subscribe<object>();
```

Refer to [DataSync SDK entities (JavaScript)](https://www.pubnub.com/docs/sdks/javascript/api-reference/publish-and-subscribe.md#datasync-sdk-entities) and [Add DataSync listener (C#)](https://www.pubnub.com/docs/sdks/c-sharp/api-reference/publish-and-subscribe.md#add-datasync-listener) for the full method reference.

##### How DataSync events compare to App Context events

DataSync events use a distinct format from App Context events. Existing App Context event consumers are unaffected, the two formats do not overlap.

| Aspect | App Context | DataSync |
| --- | --- | --- |
| Message type | 2 | 5 |
| `source` | `objects` | `data-sync` |
| Verbs | set, delete | create, update, delete |
| Envelope version | 2.0 | 1.0 |

## Terms in this document

* **Channel** - A pathway for sending and receiving messages between devices, created automatically when you first use it, that can handle any number of users and messages for different communication needs, like 1-1 text chats, group conversations, and other data streaming.
* **Class** - A versioned type definition (name plus integer version) for entities or relationships in DataSync. Classes carry property definitions that declare which payload fields are validated, filterable, and scoped by projections.
* **Membership** - A relationship in DataSync that links a channel to a user, using the built-in many-to-many Membership class.
* **Projection** - A named view over an object's payload fields in DataSync, controlling which fields a client can read and write based on its Access Manager token.
* **Relationship** - A typed link between two entities in DataSync, with its own payload and system fields, typed by a relationship class that declares cardinality.
* **User** - An individual or entity that interacts with a system, application, or service. In PubNub, a user typically refers to someone who sends or receives messages through the platform, identified by a unique user ID or username.

Last updated at: 2026-09-08T16:52:30.000Z
