Event listeners overview
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 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 in the first place. For the procedure that creates a subscription and registers listeners on it, refer to Receive messages.
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.
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.
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: 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.
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 for the categories it reports and to 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
- Python
- Java
- Kotlin
- C#
- Go
- Rust
- C-Core (legacy)
- Swift
- Objective-C
- Dart
- PHP
- Ruby
- Unity
- Unreal Engine
1
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.
1subscription = pubnub.channel('channel_1').subscription()
2
3# Style 1: a dedicated property per event type
4subscription.on_message = lambda message: print('Message received:', message.message)
5
6
7# Style 2: one class implementing several handlers, registered with add_listener()
8class PrintListener(SubscribeCallback):
9 def message(self, message):
10 print('Message received:', message.message)
11
12
13subscription.add_listener(PrintListener())
14subscription.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.
1
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.
1
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.
1
subscription1.onMessage += ... is the dedicated-property style. subscription.AddListener(eventListener), where eventListener is a SubscribeCallbackExt covering several event types, is the generic style.
1
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.
1let subscription = pubnub.channel("channel_1").subscription(None);
2subscription.subscribe();
3
4// Style 1: a dedicated stream per event type
5tokio::spawn(subscription.messages_stream().for_each(|message| async move {
6 println!("Message received: {:?}", message.data);
7}));
8
9// Style 2: one combined stream yielding a tagged enum of every event type
10tokio::spawn(subscription.stream().for_each(|event| async move {
11 if let Update::Message(message) = event {
12 println!("Message received: {:?}", message.data);
13 }
14}));
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.
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 printf("Message received: %s\n", message);
5 }
6}
7
8pubnub_subloop_t *loop = pubnub_subloop_define(pubnub, "channel_1", pubnub_subscribe_defopts(), subloop_callback);
9pubnub_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.
1
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.
1@interface MyListener : NSObject <PNEventsListener>
2@end
3
4@implementation MyListener
5
6- (void)client:(PubNub *)client didReceiveMessage:(PNMessageResult *)message {
7 NSLog(@"Message received: %@", message.data.message);
8}
9
10@end
11
12MyListener *listener = [MyListener new];
13[pubnub addListener:listener];
14[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.
1final subscription = pubnub.subscribe(channels: {'channel_1'}, withPresence: true);
2
3// Style 1: a dedicated stream for one event type
4subscription.presence.listen((event) {
5 print('Presence event: ${event.action}');
6});
7
8// Style 2: one combined stream carrying every event type, switching on messageType
9subscription.messages.listen((envelope) {
10 switch (envelope.messageType) {
11 case MessageType.normal:
12 print('Message received: ${envelope.payload}');
13 break;
14 default:
15 break;
show all 17 linesDart has no dedicated-property or addListener() style. Instead it exposes one stream per category: .presence is the dedicated style, emitting only presence 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.
1class MyListener extends SubscribeCallback
2{
3 public function message($pubnub, $message)
4 {
5 echo 'Message received: ' . json_encode($message->getMessage()) . PHP_EOL;
6 }
7
8 public function presence($pubnub, $presence) {}
9 public function status($pubnub, $status) {}
10}
11
12$pubnub->addListener(new MyListener());
13$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.
1callback = Pubnub::SubscribeCallback.new(
2 message: ->(envelope) { puts "Message received: #{envelope.result[:data][:message]}" },
3 presence: ->(envelope) {}
4)
5
6pubnub.add_listener(callback: callback)
7pubnub.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.
1
subscription1.onMessage += ... is the dedicated-property style. subscription.AddListener(eventListener), where eventListener is a SubscribeCallbackListener covering several event types, is the generic style.
1
1
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.
Listener scope follows the subscription, not the channel name
A subscription covers one entity, and a subscription set covers several as one unit. 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.
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.
Next steps
- Receive messages. Create a subscription and register handlers on it.
- Events. Every event type, what triggers it, and the fields its payload carries.
- Subscribe. Subscriptions, subscription sets, and what a client is eligible to receive.
- Subscriptions. How a subscription and a subscription set are built and scoped.
- Stop receiving messages. Remove a handler and unsubscribe.
- Connection management. The status listener, reconnection policies, and recovery.