---
source_url: https://www.pubnub.com/docs/pub-sub/subscribe/event-listeners
title: Event listeners overview
updated_at: 2026-09-30T07:20:08.000Z
---

# Event listeners overview

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

An event listener is a callback your code registers to receive one specific kind of event PubNub delivers to a subscription. [PubNub generates an event](https://www.pubnub.com/docs/architecture/events.md) whenever something meaningful happens on a channel your client is subscribed to, such as a message being published, a signal being sent, or a user's presence changing. The listener is how that event reaches your application code. This page explains:

* what a listener is, and what makes it run
* why there is one handler per event type instead of one generic callback
* the two ways SDKs let you register handlers
* how listener scope follows what the listener is attached to
* how a listener's lifecycle differs from the subscription it is attached to

One rule holds throughout. A listener only ever receives what its subscription receives, so nothing on this page substitutes for [subscribing to the right channels](https://www.pubnub.com/docs/pub-sub/subscribe/overview.md) in the first place. For the procedure that creates a subscription and registers listeners on it, refer to [Receive messages](https://www.pubnub.com/docs/pub-sub/subscribe/receive-messages.md).

## A listener is a callback, not a connection

A listener does not open anything and does not fetch anything on its own. It is a function your code hands to the SDK, and the SDK calls it when a matching event arrives on whatever the listener is attached to, a [subscription or a subscription set](https://www.pubnub.com/docs/pub-sub/subscribe/overview.md#what-a-subscription-addresses).

Because a listener is only a callback, attaching one does nothing by itself. A subscription with every handler registered still receives nothing until it is started, so registering handlers and calling `subscribe()` are two separate steps. The order between them does not matter as long as both happen. A handler registered before or after the subscription starts fires identically once it is running. For that procedure, refer to [Receive messages](https://www.pubnub.com/docs/pub-sub/subscribe/receive-messages.md).

## One handler per event type

PubNub routes each event type to its own dedicated handler rather than delivering a single generic callback. The handlers are [named consistently across entity-based SDKs](https://www.pubnub.com/docs/architecture/events.md#client-side-events): `onMessage`, `onSignal`, `onPresence`, `onObjects`, `onMessageAction`, and `onFile`.

Splitting by handler instead of by field means your code never inspects a payload to work out what kind of event it is looking at. The routing already happened before the callback ran. A chat client that treats a text message differently from a typing signal writes that distinction once, as two separate handlers that each only ever receive one kind of event. That beats a branch inside a single handler for everything.

For the payload each handler receives and the conditions each event type depends on, such as an add-on that must be enabled on your keyset, refer to [Events](https://www.pubnub.com/docs/architecture/events.md).

### The status handler is the exception

Connection status is not an event on a channel. It reports the state of the subscribe connection, which is shared by every subscription and subscription set the client holds. For that reason the status handler is registered on the PubNub client object, never on an individual subscription, even in SDKs where every other handler is entity-based. Refer to [Connection status events](https://www.pubnub.com/docs/architecture/events.md#connection-status) for the categories it reports and to [The status listener](https://www.pubnub.com/docs/architecture/connection-management/overview.md#the-status-listener) for what to do with each one.

## Two ways to register a handler

Most SDKs offer the same handlers through two different call shapes. One is a dedicated property or setter per event type. The other is a single call that registers a generic listener covering several event types at once. Both register the same callback on the same subscription, and the difference is ergonomics, not behavior. A few SDKs expose only one shape. The language has no entity model to attach a per-event property to, and no generic listener object either, such as Go's single channel-based `Listener`. Those tabs below show the one mechanism that exists rather than a fabricated second one.

### JavaScript

```javascript
// create a subscription from a channel entity
const channel = pubnub.channel('channel_1');
const subscription1 = channel.subscription({ receivePresenceEvents: true });

// create a subscription set with multiple channels
const subscriptionSet1 = pubnub.subscriptionSet({ channels: ['ch1', 'ch2'] });

// add a status listener
pubnub.addListener({
  status: (s) => {
    console.log('Status', s.category);
  },
});

// add message and presence listeners
subscription1.addListener({
  // Messages
  message: (m) => {
    console.log('Received message', m);
  },
  // Presence
  presence: (p) => {
    console.log('Presence event', p);
  },
});

// add event-specific message actions listener
subscriptionSet1.onMessageAction = (p) => {
  console.log('Message action event:', p);
};

subscription1.subscribe();
subscriptionSet1.subscribe();
```

`subscription1.addListener({ message, presence })` registers a generic listener covering several event types at once. `subscriptionSet1.onMessageAction` is the dedicated-property style, and it works the same way on a plain subscription.

### Python

```python
subscription = pubnub.channel('channel_1').subscription()

# Style 1: a dedicated property per event type
subscription.on_message = lambda message: print('Message received:', message.message)

# Style 2: one class implementing several handlers, registered with add_listener()
class PrintListener(SubscribeCallback):
    def message(self, message):
        print('Message received:', message.message)

subscription.add_listener(PrintListener())
subscription.subscribe()
```

Both styles are scoped to `subscription` and fire only for its channel. A listener class added this way receives one argument per event method (`message`, `presence`, and so on). A listener class added to the `pubnub` client instead receives the client as a second argument. A class written for one scope therefore needs a small adjustment to work at the other.

### Java

```java
subscription.setOnMessage((PNMessageResult pnMessageResult) -> {
    JsonElement message = pnMessageResult.getMessage();
    String channel1 = pnMessageResult.getChannel();
    String publisher = pnMessageResult.getPublisher();
    String subscription1 = pnMessageResult.getSubscription();
    String customMessageType = pnMessageResult.getCustomMessageType();
    Long timetoken = pnMessageResult.getTimetoken();
});

subscription.setOnSignal((PNSignalResult pnSignalResult) -> {
    JsonElement message = pnSignalResult.getMessage();
    String channel = pnSignalResult.getChannel();
    String publisher = pnSignalResult.getPublisher();
    String subscription1 = pnSignalResult.getSubscription();
    String customMessageType = pnSignalResult.getCustomMessageType();
    Long timetoken = pnSignalResult.getTimetoken();
});

subscription.setOnMessageAction((PNMessageActionResult pnMessageActionResult) -> {
    PNMessageAction messageAction = pnMessageActionResult.getMessageAction();
    String channel = pnMessageActionResult.getChannel();
    String event = pnMessageActionResult.getEvent();
    String publisher = pnMessageActionResult.getPublisher();
    String subscription1 = pnMessageActionResult.getSubscription();
    Long timetoken = pnMessageActionResult.getTimetoken();
});

subscription.setOnFile((PNFileEventResult pnFileEventResult) -> {
    JsonElement message = (JsonElement) pnFileEventResult.getMessage();
    PNDownloadableFile file = pnFileEventResult.getFile();
    String channel = pnFileEventResult.getChannel();
    String publisher = pnFileEventResult.getPublisher();
    String subscription1 = pnFileEventResult.getSubscription();
    String customMessageType = pnFileEventResult.getCustomMessageType();
    Long timetoken = pnFileEventResult.getTimetoken();
});

subscription.setOnUuidMetadata((PNUUIDMetadataResult pnUUIDMetadataResult) -> {
    String event = pnUUIDMetadataResult.getEvent();
    PNUUIDMetadata data = pnUUIDMetadataResult.getData();
    String channel = pnUUIDMetadataResult.getChannel();
    String publisher = pnUUIDMetadataResult.getPublisher();
    String subscription1 = pnUUIDMetadataResult.getSubscription();
    Long timetoken = pnUUIDMetadataResult.getTimetoken();
});

subscription.setOnChannelMetadata((PNChannelMetadataResult pnChannelMetadataResult) -> {
    String event = pnChannelMetadataResult.getEvent();
    PNChannelMetadata data = pnChannelMetadataResult.getData();
    String channel = pnChannelMetadataResult.getChannel();
    String publisher = pnChannelMetadataResult.getPublisher();
    String subscription1 = pnChannelMetadataResult.getSubscription();
    Long timetoken = pnChannelMetadataResult.getTimetoken();
});

subscription.setOnMembership((PNMembershipResult pnMembershipResult) -> {
    String event = pnMembershipResult.getEvent();
    PNMembership data = pnMembershipResult.getData();
    String channel = pnMembershipResult.getChannel();
    String publisher = pnMembershipResult.getPublisher();
    String subscription1 = pnMembershipResult.getSubscription();
    Long timetoken = pnMembershipResult.getTimetoken();
});

subscription.setOnPresence((PNPresenceEventResult pnPresenceEventResult) -> {
    String event = pnPresenceEventResult.getEvent();
    Integer occupancy = pnPresenceEventResult.getOccupancy();
    String channel = pnPresenceEventResult.getChannel();
    List<String> join = pnPresenceEventResult.getJoin();
    List<String> leave = pnPresenceEventResult.getLeave();
    JsonElement state = pnPresenceEventResult.getState();
    List<String> timeout = pnPresenceEventResult.getTimeout();
    String subscription1 = pnPresenceEventResult.getSubscription();
    Long timetoken = pnPresenceEventResult.getTimetoken();
});
// Create a subscription to a specific channel
Subscription subscription = pubNub.channel("my_channel").subscription(SubscriptionOptions.receivePresenceEvents());

subscription.addListener(new EventListener() {
    @Override
    public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult result) {
        // Log or process message
        System.out.println("Message: " + result.getMessage());
    }

    @Override
    public void presence(@NotNull PubNub pubnub, @NotNull PNPresenceEventResult result) {
        // Handle presence updates
        // requires a subscription with presence
        System.out.println("Presence userId: " + result.getUuid() + ", Event: " + result.getEvent());
    }

    @Override
    public void signal(@NotNull PubNub pubnub, @NotNull PNSignalResult result) {
        // Handle signals
        System.out.println("Signal: " + result.getMessage());
    }

    @Override
    public void messageAction(@NotNull PubNub pubNub, @NotNull PNMessageActionResult result) {
        // Handle message reactions
        System.out.println("Message Reaction: " + result.getData());
    }

    @Override
    public void file(@NotNull PubNub pubnub, @NotNull PNFileEventResult result) {
        // Handle file events
        System.out.println("File: " + result.getFile().getName());
    }

    @Override
    public void uuid(@NotNull PubNub pubnub, @NotNull PNUUIDMetadataResult pnUUIDMetadataResult) {
        // Handle uuid metadata events
        System.out.println("UUID: " + pnUUIDMetadataResult.getData().getName());
    }

    @Override
    public void channel(@NotNull PubNub pubnub, @NotNull PNChannelMetadataResult pnChannelMetadataResult) {
        // Handle channel metadata events
        System.out.println("Channel: " + pnChannelMetadataResult.getData().getName());
    }

    @Override
    public void membership(@NotNull PubNub pubnub, @NotNull PNMembershipResult pnMembershipResult) {
        // Handle membership metadata events
        System.out.println("Channel: " + pnMembershipResult.getData().getChannel().getName() + " UUID: "
                + pnMembershipResult.getData().getUuid());
    }
});

// Activate the subscription to start receiving events
subscription.subscribe();

// Print a status when successfully subscribed
System.out.println("Subscribed to channel 'my_channel'");

Subscription subscription02 = pubNub.channel("my_channel02").subscription();
Set<Subscription> subscriptionsSet = new HashSet<>();
subscriptionsSet.add(subscription);
subscriptionsSet.add(subscription02);

SubscriptionSet subscriptionSet = pubNub.subscriptionSetOf(subscriptionsSet);

// add global listener that listen on all subscribed channels
pubNub.addListener(new EventListener() {
    @Override
    public void message(@NotNull PubNub pubnub, @NotNull PNMessageResult message) {
        System.out.println("Message: " + message.getMessage());
    }
});

// Activate the subscriptionSet to start receiving events
subscriptionSet.subscribe();
```

`setOnMessage()` and the other `setOn*` methods are the dedicated-property style. `addListener(new EventListener() {...})` is the generic style, registering several handlers on the same subscription in one call.

### Kotlin

```kotlin
subscription.onMessage = { message ->
    // Handle message
}

subscription.onSignal = { signal ->
    // Handle signal
}

subscription.onMessageAction = { messageAction ->
    // Handle message reaction
}

subscription.onFile = { file ->
    // Handle file event
}

subscription.onObjects = { obj ->
    // Handle metadata updates
}

subscription.onPresence = { presence ->
    // Handle presence updates
}

val onMessage: (PNMessageResult) -> Unit = { /* Handle message */ }
val onSignal: (PNSignalResult) -> Unit = { /* Handle signal */ }
val onMessageAction: (PNMessageActionResult) -> Unit = { /* Handle message reaction */ }
val onFile: (PNFileEventResult) -> Unit = { /* Handle file event */ }
val onObjects: (PNObjectEventResult) -> Unit = { /* Handle metadata updates */ }
val onPresence: (PNPresenceEventResult) -> Unit = { /* Handle presence updates */ }

subscription.onMessage = onMessage
subscription.onSignal = onSignal
subscription.onMessageAction = onMessageAction
subscription.onFile = onFile
subscription.onObjects = onObjects
subscription.onPresence = onPresence

// Create a subscription to a specific channel
val subscription = pubnub.channel("my_channel").subscription()

// Add a listener to the subscription for handling various event types
subscription.addListener(object : EventListener {
    override fun message(pubnub: PubNub, message: PNMessageResult) {
        // Log or process message
        println("Message: ${message.message}")
    }

    override fun signal(pubnub: PubNub, signal: PNSignalResult) {
        // Handle signals
        println("Signal: ${signal.message}")
    }

    override fun messageAction(pubnub: PubNub, messageAction: PNMessageActionResult) {
        // Handle message reactions
        println("Message Reaction: ${messageAction.data}")
    }

    override fun file(pubnub: PubNub, file: PNFileEventResult) {
        // Handle file events
        println("File: ${file.file.name}")
    }

    override fun objects(pubnub: PubNub, obj: PNObjectEventResult) {
        // Handle metadata updates
        println("App Context: ${obj.extractedMessage.event}")
    }

    override fun presence(pubnub: PubNub, presence: PNPresenceEventResult) {
        // Handle presence updates
        // requires a subscription with presence
        println("Presence: ${presence.uuid} - ${presence.event}")
    }
})

// Activate the subscription to start receiving events
subscription.subscribe()

// Print a status when successfully subscribed
println("Subscribed to channel 'my_channel'")

// Create subscription set
val subscriptionSet = pubnub.subscriptionSetOf(
    // Specify channels with default options
    channels = setOf("my_channel", "other_channel"),
)

// Add listener to the subscriptionSet
subscriptionSet.addListener(object : EventListener {
    override fun message(pubnub: PubNub, message: PNMessageResult) {
        // Log or process message
        println("Message: ${message.message}")
    }
})

// Activate the subscriptionSet to start receiving events
subscriptionSet.subscribe()
```

`subscription.onMessage = { ... }` and the other `on*` properties are the dedicated style. `subscription.addListener(object : EventListener {...})` is the generic style, registering several handlers on the same subscription in one call.

### C#

```csharp
// Add event-specific listeners
// Add a listener to receive Message changes
Subscription subscription1 = pubnub.Channel("channelName").Subscription();

subscription1.onMessage += (Pubnub pn, PNMessageResult<object> messageEvent) =>
{
    Console.WriteLine($"Message received {messageEvent.Message}");
};

subscription1.Subscribe<object>();

// Add multiple listeners
SubscribeCallbackExt eventListener = new SubscribeCallbackExt(
    delegate(Pubnub pn, PNMessageResult<object> messageEvent)
    {
        Console.WriteLine($"received message {messageEvent.Message}");
    },
    delegate(Pubnub pn, PNPresenceEventResult e) { Console.WriteLine("Presence event"); },
    delegate(Pubnub pn, PNSignalResult<object> e) { Console.WriteLine("Signal event"); },
    delegate(Pubnub pn, PNObjectEventResult e) { Console.WriteLine("Object event"); },
    delegate(Pubnub pn, PNMessageActionEventResult e) { Console.WriteLine("Message Action event"); },
    delegate(Pubnub pn, PNFileEventResult e) { Console.WriteLine("File event"); }
);

Channel firstChannel = pubnub.Channel("first");
var subscription = firstChannel.Subscription(SubscriptionOptions.ReceivePresenceEvents);
subscription.AddListener(eventListener);
subscription.Subscribe<object>();
```

`subscription1.onMessage += ...` is the dedicated-property style. `subscription.AddListener(eventListener)`, where `eventListener` is a `SubscribeCallbackExt` covering several event types, is the generic style.

### Go

```go
// Example_subscribe demonstrates basic channel subscription
func Example_subscribe() {
	config := pubnub.NewConfigWithUserId(pubnub.UserId("demo-user"))
	config.SubscribeKey = "demo"

	pn := pubnub.NewPubNub(config)

	// Create listener to handle incoming messages
	listener := pubnub.NewListener()

	// Create a done channel to stop the goroutine when needed
	done := make(chan bool)

	go func() {
		for {
			select {
			case status := <-listener.Status:
				// Handle connection status changes
				switch status.Category {
				case pubnub.PNConnectedCategory:
					fmt.Println("Connected to PubNub")
				case pubnub.PNReconnectedCategory:
					fmt.Println("Reconnected to PubNub")
				case pubnub.PNDisconnectedCategory:
					fmt.Println("Disconnected from PubNub")
				}

			case message := <-listener.Message:
				// Handle received messages
				fmt.Printf("Received message: %v on channel: %s\n",
					message.Message, message.Channel)

			case <-done:
				// Stop the goroutine when done signal is received
				return
			}
		}
	}()

	// Add listener and subscribe to channel
	pn.AddListener(listener)

	pn.Subscribe().
		Channels([]string{"my-channel"}).
		Execute()

	fmt.Println("Subscribed to channel")

	// When done, unsubscribe and stop goroutine
	pn.UnsubscribeAll()
	close(done)

}
```

Go has one registration mechanism, not two. A single `Listener` struct exposes every event type as its own Go channel, and you add it once with `AddListener`. There is no separate dedicated-property style to contrast it with.

### Rust

```rust
let subscription = pubnub.channel("channel_1").subscription(None);
subscription.subscribe();

// Style 1: a dedicated stream per event type
tokio::spawn(subscription.messages_stream().for_each(|message| async move {
    println!("Message received: {:?}", message.data);
}));

// Style 2: one combined stream yielding a tagged enum of every event type
tokio::spawn(subscription.stream().for_each(|event| async move {
    if let Update::Message(message) = event {
        println!("Message received: {:?}", message.data);
    }
}));
```

`messages_stream()` is the dedicated style: one stream per event type. `stream()` is the generic style, yielding an `Update` enum you match on, covering every event type on the same subscription through one stream.

### C-Core (legacy)

:::note New SDK available
C-Core (legacy) is still supported. If you are starting a new project, use the new [C SDK](https://www.pubnub.com/docs/sdks/c.md).
:::

```c
static void subloop_callback(pubnub_t *pbp, char const *message, enum pubnub_res result)
{
    if (PNR_OK == result) {
        printf("Message received: %s\n", message);
    }
}

pubnub_subloop_t *loop = pubnub_subloop_define(pubnub, "channel_1", pubnub_subscribe_defopts(), subloop_callback);
pubnub_subloop_start(loop);
```

C-Core (legacy) has one listener mechanism, and it lives in a separate build from the synchronous interface used elsewhere in this SDK's quickstart. That build is the callback interface, built as `pubnub_callback.a` instead of `pubnub_sync.a`. `pubnub_subloop_define()` binds a callback to a channel, and `pubnub_subloop_start()` runs the loop, invoking that callback for each message received. The synchronous build has no listener at all, which is why code written against it polls with `pubnub_get()` instead.

### Swift

```swift
// Defines a custom type that can be used to decode the message payload
struct Person: JSONCodable {
  var lastName: String
  var firstName: String
  var age: Int
}

// Add a listener for Message events
subscription.onMessage = { message in
  // Example showing how to decode the message payload as the custom Person type defined above
  if let person = try? message.payload.decode(Person.self) {
    print("Person object decoded successfully")
    print("Person details: \(person.lastName), \(person.firstName), \(person.age)")
  }
  // Example showing how to decode the message payload as a raw [String: Any] dictionary
  else if let dictionary = message.payload.codableValue.dictionaryOptional {
    print("Dictionary decoded successfully: \(dictionary)")
  }
  // Example showing how to decode the message payload as a raw [Any] array
  else if let array = message.payload.codableValue.arrayOptional {
    print("Array decoded successfully: \(array)")
  }
  // Example showing how to decode the message payload as a String scalar value.
  // If you need other scalar types, you can use the properties listed below:
  //
  // - .intOptional - to decode payload as an Int value
  // - .boolOptional - to decode payload as a Bool value
  // - .doubleOptional - to decode payload as a Double value
  else if let scalarValue = message.payload.codableValue.stringOptional {
    print("Scalar value: \(scalarValue)")
  }
  // Fallback when the message payload cannot be decoded
  else {
    print("Failed to decode the message payload")
  }
}
// Add a listener to capture single event
subscription.onEvent = { event in
  switch event {
  case let .messageReceived(message):
    print("Message Received: \(message) Publisher: \(message.publisher ?? "defaultUUID")")
  case let .signalReceived(signal):
    print("Signal Received: \(signal)")
  case let .presenceChanged(presence):
    print("Presence event: \(presence)")
  case let .appContextChanged(appContextEvent):
    print("App Context change event: \(appContextEvent)")
  case let .messageActionChanged(messageActionEvent):
    print("Message Reaction event: \(messageActionEvent)")
  case let .fileChanged(fileEvent):
    print("File event: \(fileEvent)")
  }
}
```

`subscription.onMessage = { ... }` is the dedicated-property style. `subscription.onEvent = { event in switch event {...} }` is the generic style, delivering one tagged event at a time on the same subscription.

### Objective-C

```objectivec
@interface MyListener : NSObject <PNEventsListener>
@end

@implementation MyListener

- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {
    NSLog(@"Message received: %@", message.data.message);
}

@end

MyListener *listener = [MyListener new];
[pubnub addListener:listener];
[pubnub subscribeToChannels:@[@"channel_1"] withPresence:NO];
```

Objective-C has one registration mechanism, not two. It's a listener object conforming to `PNEventsListener`, with one delegate method per event type, added with `addListener:`. There is no block-based alternative to contrast it with.

### Dart

```dart
final subscription = pubnub.subscribe(channels: {'channel_1'}, withPresence: true);

// Style 1: a dedicated stream for one event type
subscription.presence.listen((event) {
  print('Presence event: ${event.action}');
});

// Style 2: one combined stream carrying every event type, switching on messageType
subscription.messages.listen((envelope) {
  switch (envelope.messageType) {
    case MessageType.normal:
      print('Message received: ${envelope.payload}');
      break;
    default:
      break;
  }
});
```

Dart has no dedicated-property or `addListener()` style. Instead it exposes one stream per category: `.presence` is the dedicated style, emitting only [presence](https://www.pubnub.com/docs/presence/overview.md) events. `.messages` is the generic style, carrying every event type on the subscription, and code consuming it switches on `envelope.messageType` to tell them apart.

### PHP

```php
class MyListener extends SubscribeCallback
{
    public function message($pubnub, $message)
    {
        echo 'Message received: ' . json_encode($message->getMessage()) . PHP_EOL;
    }

    public function presence($pubnub, $presence) {}
    public function status($pubnub, $status) {}
}

$pubnub->addListener(new MyListener());
$pubnub->subscribe()->channels('channel_1')->execute();
```

PHP has one registration mechanism, not two. It's a `SubscribeCallback` subclass with one method per event type, added with `addListener()` on the client. There is no per-subscription dedicated-property alternative.

### Ruby

```ruby
callback = Pubnub::SubscribeCallback.new(
  message: ->(envelope) { puts "Message received: #{envelope.result[:data][:message]}" },
  presence: ->(envelope) {}
)

pubnub.add_listener(callback: callback)
pubnub.subscribe(channels: ['channel_1'])
```

Ruby has one registration mechanism, not two. It's a `SubscribeCallback` built from one lambda per event type, added with `add_listener()` on the client. There is no per-subscription dedicated-property alternative.

### Unity

```csharp
// Add event-specific listeners
// Add a listener to receive Message changes
Subscription subscription1 = pubnub.Channel("channelName").Subscription();

subscription1.onMessage += (Pubnub pn, PNMessageResult<object> messageEvent) =>
{
    Debug.Log($"Message received {messageEvent.Message}");
};

subscription1.Subscribe<object>();

// Add multiple listeners
SubscribeCallbackListener eventListener = new SubscribeCallbackListener(
    delegate(Pubnub pn, PNMessageResult<object> messageEvent)
    {
        Debug.Log($"received message {messageEvent.Message}");
    },
    delegate(Pubnub pn, PNPresenceEventResult e) { Debug.Log("Presence event"); },
    delegate(Pubnub pn, PNSignalResult<object> e) { Debug.Log("Signal event"); },
    delegate(Pubnub pn, PNObjectEventResult e) { Debug.Log("Object event"); },
    delegate(Pubnub pn, PNMessageActionEventResult e) { Debug.Log("Message Action event"); },
    delegate(Pubnub pn, PNFileEventResult e) { Debug.Log("File event"); },
    delegate(Pubnub pn, PNStatus e) { Debug.Log("Status event"); }
);

Channel firstChannel = pubnub.Channel("first");
var subscription = firstChannel.Subscription(SubscriptionOptions.ReceivePresenceEvents);
subscription.AddListener(eventListener);
subscription.Subscribe<object>();
```

`subscription1.onMessage += ...` is the dedicated-property style. `subscription.AddListener(eventListener)`, where `eventListener` is a `SubscribeCallbackListener` covering several event types, is the generic style.

### Unreal Engine

```cpp
// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::SubscribeWithChannelEntitySample()
{
	
	//Assumes PubnubClient is created and UserID is set

	// Create a channel entity for the channel you want to work with
	FString ChannelName = TEXT("game_lobby");
	UPubnubChannelEntity* ChannelEntity = PubnubClient->CreateChannelEntity(ChannelName);

	// Create a subscription from the channel entity
	UPubnubSubscription* ChannelSubscription = ChannelEntity->CreateSubscription();

	// Add message listener to the subscription
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	ChannelSubscription->OnPubnubMessage.AddDynamic(this, &ASample_ChannelEntity::OnMessageReceived_ChannelEntitySample);

	// Subscribe to start receiving messages
	ChannelSubscription->SubscribeAsync();
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnMessageReceived_ChannelEntitySample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("Channel Entity - Message received: %s on channel: %s"), *Message.Message, *Message.Channel);
}
```

```cpp
// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::ChannelEntityAllListenersSample()
{
	
	//Assumes PubnubClient is created and UserID is set

	// Create a channel entity
	FString ChannelName = TEXT("comprehensive_channel");
	UPubnubChannelEntity* ChannelEntity = PubnubClient->CreateChannelEntity(ChannelName);

	// Create a subscription with presence events enabled to receive all event types
	FPubnubSubscribeSettings SubscriptionSettings;
	SubscriptionSettings.ReceivePresenceEvents = true;
	UPubnubSubscription* Subscription = ChannelEntity->CreateSubscription(SubscriptionSettings);

	// Add ALL listener types to handle different PubNub events

	// 1. Message Listener - Fires when regular messages are published to the channel
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	Subscription->OnPubnubMessage.AddDynamic(this, &ASample_ChannelEntity::OnMessage_AllListenersSample);

	// 2. Signal Listener - Fires when signals are sent to the channel
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	Subscription->OnPubnubSignal.AddDynamic(this, &ASample_ChannelEntity::OnSignal_AllListenersSample);

	// 3. Presence Event Listener - Fires when users join/leave/timeout on the channel
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	Subscription->OnPubnubPresenceEvent.AddDynamic(this, &ASample_ChannelEntity::OnPresenceEvent_AllListenersSample);

	// 4. Object Event Listener - Fires when App Context metadata changes occur
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	Subscription->OnPubnubObjectEvent.AddDynamic(this, &ASample_ChannelEntity::OnObjectEvent_AllListenersSample);

	// 5. Message Action Listener - Fires when message actions/reactions are added or removed
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	Subscription->OnPubnubMessageAction.AddDynamic(this, &ASample_ChannelEntity::OnMessageAction_AllListenersSample);

	// 6. Universal Listener - Fires for ANY type of PubNub event (catch-all)
	// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
	Subscription->FOnPubnubAnyMessageType.AddDynamic(this, &ASample_ChannelEntity::OnAnyEvent_AllListenersSample);

	// Subscribe to start receiving all event types
	Subscription->SubscribeAsync();
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnMessage_AllListenersSample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("MESSAGE LISTENER - Content: %s, Channel: %s, User: %s"), 
		*Message.Message, *Message.Channel, *Message.UserID);
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnSignal_AllListenersSample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("SIGNAL LISTENER - Content: %s, Channel: %s, User: %s"), 
		*Message.Message, *Message.Channel, *Message.UserID);
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnPresenceEvent_AllListenersSample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("PRESENCE LISTENER - Event: %s, Channel: %s"), 
		*Message.Message, *Message.Channel);
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnObjectEvent_AllListenersSample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("OBJECT EVENT LISTENER - Event: %s, Channel: %s"), 
		*Message.Message, *Message.Channel);
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnMessageAction_AllListenersSample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("MESSAGE ACTION LISTENER - Action: %s, Channel: %s"), 
		*Message.Message, *Message.Channel);
}

// ACTION REQUIRED: Replace ASample_ChannelEntity with name of your Actor class
void ASample_ChannelEntity::OnAnyEvent_AllListenersSample(FPubnubMessageData Message)
{
	UE_LOG(LogTemp, Log, TEXT("UNIVERSAL LISTENER - Type: %d, Content: %s, Channel: %s"), 
		(int32)Message.MessageType, *Message.Message, *Message.Channel);
}
```

`Subscription->OnPubnubMessage.AddDynamic(...)` and the other `OnPubnub*` delegates are the dedicated style, one per event type. `Subscription->FOnPubnubAnyMessageType.AddDynamic(...)`, shown alongside all five dedicated delegates in the second sample, is the generic style: a single catch-all delegate that fires for every event type on the same subscription.

Where an SDK offers both styles, the dedicated-property style suits handlers you assign or replace individually, since setting the property again replaces the previous callback. The generic-listener style suits registering a full set of handlers in one call. Neither style changes what a handler receives or when it fires. For the setup steps around these calls, refer to [Receive messages](https://www.pubnub.com/docs/pub-sub/subscribe/receive-messages.md).

## Listener scope follows the subscription, not the channel name

[A subscription covers one entity, and a subscription set covers several as one unit](https://www.pubnub.com/docs/pub-sub/subscribe/overview.md#what-a-subscription-addresses). A handler registered on a subscription fires only for events on the entity that subscription was built from. A handler registered on a subscription set fires for events from every member of that set. Two separate subscriptions to the same channel run their own handlers independently, so removing one does not touch the other.

SDKs written before entities existed skip this scoping entirely. They register every handler on the PubNub client object with `addListener()`. Each one then fires for every channel and channel group that client is currently subscribed to, with no per-channel separation available. The event types and the fields each payload carries are identical either way, and only the registration mechanism and the resulting scope differ. Not every SDK supports entities yet, so check the API reference for your platform before assuming per-subscription scoping is available. Refer to [SDK entities](https://www.pubnub.com/docs/architecture/core-concepts.md#message).

## A listener's lifecycle is independent of its subscription's

Registering a handler and starting a subscription are separate operations, and so are removing a handler and stopping one. A handler stays registered, and keeps firing, until your code removes it explicitly. Restarting or reconfiguring the subscription it is attached to does not clear it.

The reverse also holds. Removing a handler stops your code from hearing about that event type, but it does not unsubscribe anything. A subscription with every handler removed still receives events. It just has nothing registered to call when one arrives. Treat "stop handling an event" and "stop receiving a channel" as two independent decisions, and make both if you mean to fully stop. For the calls that do each one, refer to [Stop receiving messages](https://www.pubnub.com/docs/pub-sub/subscribe/stop-receiving-messages.md).

## Next steps

* [Receive messages](https://www.pubnub.com/docs/pub-sub/subscribe/receive-messages.md). Create a subscription and register handlers on it.
* [Events](https://www.pubnub.com/docs/architecture/events.md). Every event type, what triggers it, and the fields its payload carries.
* [Subscribe](https://www.pubnub.com/docs/pub-sub/subscribe/overview.md). Subscriptions, subscription sets, and what a client is eligible to receive.
* [Subscriptions](https://www.pubnub.com/docs/pub-sub/subscribe/subscriptions.md). How a subscription and a subscription set are built and scoped.
* [Stop receiving messages](https://www.pubnub.com/docs/pub-sub/subscribe/stop-receiving-messages.md). Remove a handler and unsubscribe.
* [Connection management](https://www.pubnub.com/docs/architecture/connection-management/overview.md). The status listener, reconnection policies, and recovery.

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