App Context events
Starting a new app? Use DataSync
DataSync is the successor to App Context. It does everything App Context does for users, channels, and memberships, and adds typed schemas, partial updates with ETags, field-level access control, and per-class expiry. Refer to How DataSync compares to App Context for a feature-by-feature comparison.
DataSync is currently available to new accounts and to accounts that are not actively using App Context. If your keysets already use App Context, keep using it for now. App Context remains fully supported and these pages stay accurate.
It travels through the same subscription and event listener infrastructure as a message or a presence update, arriving through a handler PubNub's docs call onObjects. This page explains:
- the fields the payload carries, and how they differ from the raw event PubNub puts on the wire
- why the payload only ever reports
setordelete, never a separate "created" - why the payload's entity-type field reads
uuidinstead ofuser - how the payload's
datadiffers between a user, a channel, and a membership event - why one SDK delivers every App Context event to a single handler while another splits it into several
- how the server-side events Events & Actions generates from the same change relate to this one
Receiving any of this requires App Context enabled on your keyset, and the matching User Metadata Events, Channel Metadata Events, or Membership Events toggle turned on for that entity type. For where each event type lands and how to turn those toggles on, refer to Real-time updates when metadata changes. For the procedure that registers a handler, refer to Event listeners.
SDKs unwrap the event before your handler sees it
An App Context event starts life as an ordinary message on the wire. It's internally typed, so a client can tell it apart from a regular published message before inspecting the payload. That raw form nests the App Context-specific fields inside a message object, alongside a source and version marker that identify the API that produced it:
1{
2 "channel": "my_channel",
3 "message": {
4 "source": "objects",
5 "version": "2.0",
6 "event": "set",
7 "type": "channel",
8 "data": { "id": "my_channel", "name": "Main channel" }
9 },
10 "subscription": "my_channel",
11 "timetoken": "17511946699655811"
12}
Most SDKs flatten this before your handler sees it, promoting event, type, and data to top-level fields alongside the channel, subscription match, and timetoken:
| Field | Description |
|---|---|
channel | The channel the event was delivered on |
subscription | The channel group or wildcard subscription match, if any |
timetoken | When the change was recorded |
publisher | The User ID that made the change |
event | set or delete |
type | Which entity changed: uuid, channel, or membership |
data | The metadata that changed |
Not every SDK flattens or exposes every field the same way.
JavaScript keeps the App Context payload under event.message. In an onObjects handler, read event.message.event, event.message.type, and event.message.data. The delivery metadata stays at the top level in event.channel, event.subscription, and event.timetoken.
Field names and types are per SDK, not platform-wide. For the exact shape your handler receives, refer to the API reference for your platform in Available SDKs.
An event only ever says set or delete
event has exactly two values, and neither one distinguishes a brand-new record from a change to an existing one. Setting a user's metadata for the first time and updating it a week later both arrive as set. A subscriber that needs to tell "this user just joined" from "this user's profile changed" has to keep its own record of what it already knew. The event alone doesn't say.
That's a deliberate difference from the server-side events Events & Actions generates from the same underlying change, which do separate a first set from a later one. Refer to Client-side and server-side events observe the same change independently below.
Why type says uuid
A change to a user's metadata reports type: "uuid" rather than type: "user". The value still identifies the same entity.
The data field's shape follows the entity
data carries a different record depending on what type says changed. The following illustrates a set event for each entity type, matching the record shape the corresponding get call returns:
- uuid
- channel
- membership
1{
2 "channel": "test-user-1",
3 "subscription": "test-user-1",
4 "timetoken": "17511946699655811",
5 "publisher": "test-user-1",
6 "event": "set",
7 "type": "uuid",
8 "data": {
9 "id": "test-user-1",
10 "name": "John Doe",
11 "email": "johndoe@pubnub.com",
12 "custom": null,
13 "updated": "2019-02-20T23:11:20.893755",
14 "eTag": "MDcyQ0REOTUtNEVBOC00QkY2LTgwOUUtNDkwQzI4MjgzMTcwCg=="
15 }
show all 16 lines1{
2 "channel": "team.blue",
3 "subscription": "team.blue",
4 "timetoken": "17511946699812340",
5 "publisher": "test-user-1",
6 "event": "set",
7 "type": "channel",
8 "data": {
9 "id": "team.blue",
10 "name": "Blue Team",
11 "description": "The channel for Blue team and no other teams.",
12 "custom": null,
13 "updated": "2019-02-20T23:11:20.893755",
14 "eTag": "RTc1NUQwNUItREMyNy00Q0YxLUJCNDItMEZDMTZDMzVCN0VGCg=="
15 }
show all 16 lines1{
2 "channel": "team.blue",
3 "subscription": "team.blue",
4 "timetoken": "17511947001234567",
5 "publisher": "test-user-1",
6 "event": "set",
7 "type": "membership",
8 "data": {
9 "channel": { "id": "team.blue" },
10 "uuid": { "id": "test-user-1" },
11 "custom": { "starred": false },
12 "updated": "2019-02-20T23:11:20.893755",
13 "eTag": "RUNDMDUwNjktNUYwRC00RTI0LUI1M0QtNUUzNkE2NkU0MEVFCg=="
14 }
15}
A delete event's data carries little or nothing, since the record it refers to no longer exists to describe. Field names inside data are per SDK like everything else on this page. Check your platform's API reference before relying on one literally.
One handler, or several, depending on your SDK
Every other client-side event type gets one dedicated handler per type. App Context is the exception some SDKs make. Because one change can be a user, a channel, or a membership, an SDK hands it to you in one of three ways:
- One combined handler. A single handler receives every entity type, and your code branches on
typeitself. Kotlin, C#, Swift, Objective-C, and Unity all follow this pattern. - Several separate handlers. One handler per entity, so your code never checks
typeat all. Java, Go, and Python all split this way, though the mechanism differs. - The same handler as any other message. Dart and PHP deliver an App Context event through the same generic handler you'd use for an ordinary published message. Your code has to inspect the payload itself to tell the two apart.
None of these change what data reaches you, only how many places you write code to receive it. The following shows each SDK's own listener code.
- JavaScript
- Python
- Java
- Kotlin
- C#
- Go
- Rust
- C-Core (legacy)
- Swift
- Objective-C
- Dart
- PHP
- Ruby
- Unity
- Unreal Engine
1const subscription = pubnub.channel('channel_1').subscription();
2
3subscription.onObjects = (event) => {
4 const { event: action, type, data } = event.message;
5
6 if (type === 'uuid') {
7 console.log('User metadata event:', action, data);
8 } else if (type === 'channel') {
9 console.log('Channel metadata event:', action, data);
10 } else if (type === 'membership') {
11 console.log('Membership event:', action, data);
12 }
13};
14
15subscription.subscribe();
1class PrintListener(SubscribeCallback):
2 def message(self, pubnub, message):
3 print('Message received:', message.message)
4
5 def uuid(self, pubnub, event):
6 print('User metadata event:', event)
7
8 def channel(self, pubnub, event):
9 print('Channel metadata event:', event)
10
11 def membership(self, pubnub, event):
12 print('Membership event:', event)
13
14
15subscription = pubnub.channel('channel_1').subscription()
show all 17 lines1
setOnUuidMetadata, setOnChannelMetadata, and setOnMembership are the three separate handlers. Nothing here checks a type field, because each method only ever receives its own entity.
1
subscription.onObjects is the one combined handler. Its PNObjectEventResult carries a type field to tell a user, channel, or membership change apart.
1
The PNObjectEventResult delegate is the one combined handler for this SDK. Its Type property, checked in the if/else if chain, is what tells a user, channel, or membership change apart.
1listener := pubnub.NewListener()
2
3go func() {
4 for {
5 select {
6 case event := <-listener.UUIDEvent:
7 fmt.Printf("User metadata event: %+v\n", event)
8 case event := <-listener.ChannelEvent:
9 fmt.Printf("Channel metadata event: %+v\n", event)
10 case event := <-listener.MembershipEvent:
11 fmt.Printf("Membership event: %+v\n", event)
12 }
13 }
14}()
15
show all 17 linesUUIDEvent, ChannelEvent, and MembershipEvent are three separate channels on the same Listener, Go's version of three separate handlers.
1tokio::spawn(subscription.stream().for_each(|event| async move {
2 match event {
3 Update::AppContext(object) => {
4 println!("App Context event: {:?}", object)
5 }
6 _ => {}
7 }
8}));
Update::AppContext is one case of the same tagged enum every event type arrives as on this combined stream.
New SDK available
C-Core (legacy) is still supported. If you are starting a new project, use the new C SDK.
1static void subloop_callback(pubnub_t *pbp, char const *message, enum pubnub_res result)
2{
3 if (PNR_OK == result) {
4 /* App Context events arrive on this same callback as any other
5 message. Check the parsed message's "type" and "event" fields
6 to tell one apart from a regular published message. */
7 printf("Message received: %s\n", message);
8 }
9}
10
11pubnub_subloop_t *loop = pubnub_subloop_define(pubnub, "channel_1", pubnub_subscribe_defopts(), subloop_callback);
12pubnub_subloop_start(loop);
C-Core (legacy) has no dedicated App Context handler. Every subscribe result, including an App Context event, arrives on this one generic callback.
1
subscription.onAppContext is the one combined handler, and the switch over its six cases is how this SDK spells type plus event together.
1@interface MyListener : NSObject <PNEventsListener>
2@end
3
4@implementation MyListener
5
6- (void)client:(PubNub *)client didReceiveObjectEvent:(PNObjectEventResult *)event {
7 if (event.data.uuidMetadata) {
8 NSLog(@"User metadata event: %@", event.data.event);
9 } else if (event.data.channelMetadata) {
10 NSLog(@"Channel metadata event: %@", event.data.event);
11 } else if (event.data.membership) {
12 NSLog(@"Membership event: %@", event.data.event);
13 }
14}
15
show all 20 linesdidReceiveObjectEvent: is the one combined handler. Its result carries uuidMetadata, channelMetadata, or membership, whichever one applies to this change.
1final subscription = pubnub.subscribe(channels: {'channel_1'});
2
3subscription.messages.listen((envelope) {
4 // App Context events arrive on this same stream as any other message.
5 // Check envelope.messageType or the payload itself to tell one apart
6 // from a regular published message.
7 print('Received: ${envelope.payload}');
8});
Dart has no separate App Context stream. Everything, including an App Context event, arrives through this one generic messages stream.
1class MyListener extends SubscribeCallback
2{
3 public function message($pubnub, $message)
4 {
5 $payload = $message->getMessage();
6
7 // App Context events arrive on this same callback as any other
8 // message. Check $payload['type'] and $payload['event'] to tell
9 // one apart from a regular published message.
10 echo 'Received: ' . json_encode($payload) . PHP_EOL;
11 }
12
13 public function presence($pubnub, $presence) {}
14 public function status($pubnub, $status) {}
15}
show all 18 linesPHP's SubscribeCallback has no method dedicated to App Context events. They arrive on the same message() method as any other published message.
1callback = Pubnub::SubscribeCallback.new(
2 message: ->(envelope) { puts "Message received: #{envelope.result[:data][:message]}" },
3 object: ->(envelope) { puts "App Context event: #{envelope.result[:data]}" }
4)
5
6pubnub.add_listener(callback: callback)
7pubnub.subscribe(channels: ['channel_1'])
The object: callback is Ruby's one combined handler, receiving every entity type on the same key.
1
Unity shares its underlying API with C#, so its PNObjectEventResult delegate is the same one-combined-handler shape, discriminated the same way by Type.
1
Subscription->OnPubnubObjectEvent is the one combined delegate for App Context events, alongside the other event delegates on the same entity subscription.
Check your SDK's API reference in Available SDKs to confirm which pattern it follows and the exact fields its result carries.
Client-side and server-side events observe the same change independently
The same set or delete that produces an onObjects event also generates a server-side event. Events & Actions can act on that event, for example by triggering a webhook or writing to a queue. These are two separate consumption paths for one underlying change, not one feeding the other. A client-side subscriber sees the event only if it's subscribed to the right channel and the keyset toggle for that entity is on. Events & Actions observes the platform directly instead, so it needs no subscription.
Events & Actions' CRUD event types name the same three entities, but split each into three distinct types instead of App Context's set and delete:
| Source | Event type | Fires when |
|---|---|---|
| Users | User created | User metadata is set for the first time |
| Users | User updated | Existing user metadata changes |
| Users | User deleted | User metadata is removed |
| Channels | Channel created | Channel metadata is set for the first time |
| Channels | Channel updated | Existing channel metadata changes |
| Channels | Channel deleted | Channel metadata is removed |
| Memberships | Membership created | A membership record is set for the first time |
| Memberships | Membership updated | An existing membership record changes |
| Memberships | Membership deleted | A membership record is removed |
The Users and Channels sources in Events & Actions also carry unrelated Presence-driven event types, such as a user's subscription starting or stopping. Only the CRUD-produced types listed above correspond to an App Context change. For the full event and action list, refer to Event / Action List.
Next steps
- App Context. The three entity types, how to turn on App Context, and where each event type is delivered.
- Event listeners. How a handler is registered and what its scope is.
- Events. How App Context events fit among PubNub's other client-side and server-side event types.
- App Context filtering. Query App Context data instead of paging through everything.
- Events & Actions. Configure a webhook, queue, or stream from a server-side event.
- Core concepts. User IDs, channels, and memberships.