---
source_url: https://www.pubnub.com/docs/data-storage/structured-data/event-format
title: DataSync event format reference
updated_at: 2026-09-30T07:20:08.000Z
---

# DataSync event format reference

## 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 PubNub messages with [message type](https://www.pubnub.com/docs/pub-sub/overview.md#message-types-categorize-traffic-on-a-shared-channel) 5. This distinguishes them from ordinary messages. Each event carries a versioned envelope with a `metadata` section and a `data` section.

## Envelope schema

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

## Metadata fields

| Field | Type | Description |
| --- | --- | --- |
| `version` | string | Envelope version. Always `1.0` for DataSync events |
| `metadata.event` | string | `create`, `update`, or `delete` |
| `metadata.source` | string | Always `data-sync` |
| `metadata.type` | string | The object kind: `user`, `channel`, `membership`, `entity`, or `relationship` |
| `metadata.className` | string | The class name, for example `Product` or `User`. Read together with `classLevel` to distinguish a built-in class from a same-named keyset-level class |
| `metadata.classLevel` | string | `Global` (PubNub-owned) or `SubKey` (keyset-level) |
| `metadata.classVersion` | number | The class version the object pins to |

`metadata.type` is derived from the Global class the class descends from, not from its name. A keyset-level class extending the built-in `User` reports `type: "user"` alongside its own `className` and `classLevel: "SubKey"`. A keyset-level class merely named `User` that extends nothing reports `type: "entity"`.

## Data field shapes

For **create** and **update** events, `data` carries the object's current state, scoped to the projection for the channel it's delivered on:

| Field | Present on |
| --- | --- |
| `id` | All object kinds |
| `createdAt` | All object kinds |
| `updatedAt` | All object kinds |
| `expiresAt` | All object kinds (omitted if not set) |
| `eTag` | All object kinds |
| `status` | All object kinds (omitted if not set or outside projection) |
| `payload` | All object kinds (omitted if empty or outside projection) |
| `entityAId` | Relationships only |
| `entityBId` | Relationships only |
| `channelId` | Memberships only |
| `userId` | Memberships only |

For **delete** events, `data` is exactly `{ id, deletedAt }` for every object kind alike. No payload or status is included.

Fields that have no value are omitted from `data` rather than sent as `null`. Read every field except `id` defensively.

## Example events

### 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`:

```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 fields instead and never `channelId` or `userId`.

## How the event is received per SDK

### JavaScript

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

The JavaScript 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.

### 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).

Using the REST subscribe endpoint directly, the raw response carrying an event looks like:

```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 SDK field reference.

## Comparison with App Context events

DataSync events use a different format from App Context events. Existing App Context event consumers are unaffected.

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

Last updated at: 2026-09-30T07:20:08.000Z
