---
source_url: https://www.pubnub.com/docs/sdks/kotlin/api-reference/publish-and-subscribe
title: Publish/Subscribe API for Kotlin SDK
updated_at: 2026-06-19T11:37:44.877Z
sdk_name: PubNub Kotlin SDK
sdk_version: 13.4.0
---

> Documentation Index
> For a curated overview of PubNub documentation, see: https://www.pubnub.com/docs/llms.txt
> For the full list of all documentation pages, see: https://www.pubnub.com/docs/llms-full.txt


# Publish/Subscribe API for Kotlin SDK

PubNub Kotlin SDK, use the latest version: 13.4.0

Install:

```bash
Add PubNub dependency to your build@13.4.0
```

:::warning Breaking changes in v9.0.0
PubNub Kotlin SDK version 9.0.0 unifies the codebases for Kotlin and [Java](https://www.pubnub.com/docs/sdks/java) SDKs, introduces a new way of instantiating the PubNub client, and changes asynchronous API callbacks and emitted [status events](https://www.pubnub.com/docs/sdks/kotlin/status-events). These changes can impact applications built with previous versions (< `9.0.0` ) of the Kotlin SDK.
For more details about what has changed, refer to [Java/Kotlin SDK migration guide](https://www.pubnub.com/docs/sdks/kotlin/migration-guides/kotlin-v9-migration-guide).
:::

PubNub delivers messages worldwide in less than 30 ms. Send a message to one recipient or broadcast to thousands of subscribers.

For higher-level conceptual details on publishing and subscribing, refer to [Connection Management](https://www.pubnub.com/docs/general/setup/connection-management) and to [Publish Messages](https://www.pubnub.com/docs/general/messages/publish).

:::tip Request execution
Most PubNub Kotlin SDK method invocations return an Endpoint object, which allows you to decide whether to perform the operation synchronously or asynchronously.
You must invoke the `.sync()` or `.async()` method on the Endpoint to execute the request, or the operation **will not** be performed.
```kotlin
val channel = pubnub.channel("channelName")
channel.publish("This SDK rules!").async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```
:::

## Publish

##### Available in entities

This method is available to use with the `Channel` entity. For more information, refer to [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel).

`publish()` sends a message to all channel subscribers. PubNub replicates the message across its points of presence and delivers it to all subscribed clients on that channel.

###### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#configuration) with the `publishKey`.
* You must create a [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel) entity where you will publish to.
* You don't have to be subscribed to a channel to publish to it.
* You cannot publish to multiple channels simultaneously.

###### Security

Secure messages with Transport Layer Security (TLS) or Secure Sockets Layer (SSL) by setting `ssl` to `true` during [initialization](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration). You can also [encrypt](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#cryptomodule) messages.

###### Message data

The message can contain any JavaScript Object Notation (JSON)-serializable data (objects, arrays, integers, strings). Avoid special classes or functions. Strings can include any UTF‑8 characters.

:::warning Don't JSON serialize
You should not JSON serialize the `message` and `meta` parameters when sending signals, messages, or files as the serialization is automatic. Pass the full object as the message/meta payload and let PubNub handle everything.
:::

###### Size

The maximum message size is 32 KiB. This includes the escaped character count and the channel name. Aim for under 1,800 bytes for optimal performance.

If your message exceeds the limit, you'll receive a `Message Too Large` error. To learn more or calculate payload size, see [Message size limits](https://www.pubnub.com/docs/general/messages/publish#message-size-limit).

:::tip Need larger messages?
Our platform is optimized for payloads up to 32 KiB. PubNub supports larger messages, but increasing the limit requires a verification of compatibility with your use case.
Talk to [our team](https://www.pubnub.com/company/contact-sales/) to discuss increasing the message size limit for your use case.
:::

###### Publish rate

You can publish as fast as bandwidth allows. There is a [soft throughput limit](https://www.pubnub.com/docs/general/setup/limits) because messages may drop if subscribers can't keep up.

For example, publishing 200 messages at once may cause the first 100 to drop if a subscriber hasn't received any yet. The in-memory queue stores only 100 messages.

###### Custom message type

You can optionally provide the `customMessageType` parameter to add your business-specific label or category to the message, for example `text`, `action`, or `poll`.

###### Best practices

* Publish to a channel serially (not concurrently).
* Verify a success return code (for example, `[1,"Sent","136074940..."]`).
* Publish the next message only after a success return code.
* On failure (`[0,"blah","<timetoken>"]`), retry.
* Keep the in-memory queue under 100 messages to avoid drops.
* Throttle bursts to meet latency needs (for example, no more than 5 messages per second).

### Method(s)

To publish to a channel, you must first create a [Channel entity](https://www.pubnub.com/docs/sdks/kotlin/entities/channel) where you provide the name of the channel you want to publish to.

```kotlin
val channel = pubnub.channel("channelName")

channel.publish(
    message: Any,
    shouldStore: Boolean,
    meta: Any,
    queryParam: Map<String, String>,
    usePost: Boolean,
    ttl: Integer,
    customMessageType: String
).async { result -> /* check result */ }
```

| Parameter | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| message | Any | Yes |  | The payload |
| shouldStore | Boolean | Optional | `account default` | Store message in history. If not specified, the decision depends on whether Message Persistence has been enabled for the key or not. |
| meta | Any | Optional | `Not set` | Metadata object which can be used with the filtering ability. |
| queryParam | Map<String, | Optional | `Not set` | One or more query parameters to be passed to the server, for analytics purposes. Overridden in case of conflicts with reserved PubNub parameters, such as UUID or `instance_id`. Accessible from the Admin Portal, and never returned in server responses. |
| usePost | Boolean | Optional | `false` | Use HTTP POST to `publish`. |
| ttl | Integer | Optional |  | Set a per message time to live in Message Persistence. 1. If `shouldStore = true`, and `ttl = 0`, the message is stored with no expiry time. 2. If `shouldStore = true` and `ttl = X` (`X` is an Integer value), the message is stored with an expiry time of `X` hours unless you have message retention set to `Unlimited` on your keyset configuration in the Admin Portal. 3. If `shouldStore = false`, the `ttl` parameter is ignored. 4. If `ttl` isn't specified, then expiration of the message defaults back to the expiry value for the key. |
| customMessageType | String | Optional |  | A case-sensitive, alphanumeric string from 3 to 50 characters describing the business-specific label or category of the message. Dashes `-` and underscores `_` are allowed. The value cannot start with special characters or the string `pn_` or `pn-`. Examples: `text`, `action`, `poll`. |

### Sample code

:::tip Reference code
This example is a self-contained code snippet ready to be run. It includes necessary imports and executes methods with console logging. Use it as a reference when working with other examples in this document.
:::

```kotlin
import com.pubnub.api.PubNub
import com.pubnub.api.UserId
import com.pubnub.api.v2.PNConfiguration

fun main() {
    println("PubNub Publish Example")
    println("======================")

    // Configure PubNub
    val userId = UserId("publish-demo-user")
    val config = PNConfiguration.builder(userId, "demo").apply {
        publishKey = "demo"
    }.build()

    // Create PubNub instance
    val pubnub = PubNub.create(config)

    // Channel to publish to
    val channelName = "demo-channel"

    // 1. Publish a simple string message
    publishSimpleMessage(pubnub, channelName)

    // 2. Publish a JSON object
    publishJsonObject(pubnub, channelName)

    // Clean up resources
    pubnub.destroy()
}

/**
 * Publishes a simple string message to a channel
 */
private fun publishSimpleMessage(pubnub: PubNub, channelName: String) {
    println("\n# Publishing simple message to channel: $channelName")

    val message = "Hello from PubNub Kotlin SDK!"

    val channel = pubnub.channel(channelName)
    channel.publish(message).async { result ->
        result.onSuccess { response ->
            println("SUCCESS: Message published")
            println("Timetoken: ${response.timetoken}")
        }.onFailure { exception ->
            println("ERROR: Failed to publish message")
            println("Error details: ${exception.message}")
        }
    }

    // Wait for the operation to complete
    Thread.sleep(2000)
}

/**
 * Publishes a JSON object to a channel
 */
private fun publishJsonObject(pubnub: PubNub, channelName: String) {
    println("\n# Publishing JSON object to channel: $channelName")

    // Create a map to represent a JSON object
    val message = mapOf(
        "title" to "Message from Kotlin",
        "content" to "This is a JSON message",
        "timestamp" to System.currentTimeMillis(),
        "sender" to "publish-demo-user"
    )

    val channel = pubnub.channel(channelName)
    channel.publish(message).async { result ->
        result.onSuccess { response ->
            println("SUCCESS: JSON object published")
            println("Timetoken: ${response.timetoken}")
            println("Message: $message")
        }.onFailure { exception ->
            println("ERROR: Failed to publish JSON object")
            println("Error details: ${exception.message}")
        }
    }

    // Wait for the operation to complete
    Thread.sleep(2000)
}
```

:::note Subscribe to the channel
Before running the above publish example, either using the [Debug Console](https://www.pubnub.com/docs/console/) or in a separate script running in a separate terminal window, [subscribe to the same channel](#subscribe) that is being published to.
:::

### Response

The `publish()` operation returns a `PNPublishResult?`:

| Parameter | Description |
| --- | --- |
| `timetoken`Type: `Long` | Returns a `Long` representation of the timetoken when the message was published. |

### Other examples

#### Publish with metadata

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}
val pubnub = PubNub.create(configBuilder.build())

val channel = pubnub.channel("myChannel")

val myMessage = JsonObject().apply {
    addProperty("text", "Hello, world")
}

channel.publish(
    message = myMessage,
    meta = mapOf("lang" to "en"),
    usePost = true,
    customMessageType = "text-message"
).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { publishResult ->
        println("Message sent, timetoken: ${publishResult.timetoken}")
    }
}
```

#### Publishing JsonArray (Google GSON)

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}
val pubnub = PubNub.create(configBuilder.build())

val channel = pubnub.channel("myChannel")

val myMessage = JsonArray().apply {
    add(32L)
    add(35L)
}

channel.publish(
    message = myMessage,
    customMessageType = "text-message"
).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { value ->
        println("Message sent, timetoken: ${value.timetoken}")
    }
}
```

#### Publishing JSONObject (org.json)

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}
val pubnub = PubNub.create(configBuilder.build())

val channel = pubnub.channel("myChannel")

val myMessage = JSONObject().apply {
    put("lat", 32L)
    put("lng", 32L)
}

channel.publish(
    message = myMessage,
    customMessageType = "text-message"
).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { value ->
        println("Message sent, timetoken: ${value.timetoken}")
    }
}
```

#### Publishing JSONArray (org.json)

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}

val pubnub = PubNub.create(configBuilder.build())

val channel = pubnub.channel("myChannel")

val myMessage = JSONArray().apply {
    put(32L)
    put(33L)
}

channel.publish(
    message = myMessage,
    customMessageType = "text-message"
).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { value ->
        println("Message sent, timetoken: ${value.timetoken}")
    }
}
```

:::note Android release builds and org.json
If you use `org.json` types and target Android, add a `-keepnames` ProGuard/R8 rule to prevent runtime `NoSuchMethodError` crashes. See [Android crashes with org.json classes](https://www.pubnub.com/docs/sdks/kotlin/troubleshoot#android-crashes-with-orgjson-classes) for details and the required rule.
:::

#### Store the published message for 10 hours

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}

val pubnub = PubNub.create(configBuilder.build())

val channel = pubnub.channel("myChannel")

val myMessage = JsonObject().apply {
    addProperty("text", "Hello, world")
}

channel.publish(
    message = myMessage,
    shouldStore = true,
    ttl = 10,
    customMessageType = "text-message"
).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { value ->
        println("Message sent, timetoken: ${value.timetoken}")
    }
}
```

## Fire

##### Available in entities

This method is available to use with the `Channel` entity. For more information, refer to [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel).

The fire endpoint sends a message to Functions event handlers and [Illuminate](https://www.pubnub.com/docs/illuminate/business-objects/external-data-sources). The message goes directly to handlers registered on the target channel and triggers their execution. The handler can read the request body. Messages sent via `fire()` aren't replicated to subscribers and aren't stored in history.

###### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#configuration) with the `publishKey`.
* You must create a [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel) entity where you will fire to.
* The message sent via `fire()` isn't replicated and won't be received by subscribers.
* The message is not stored in history.

### Method(s)

To fire to a channel, you must first create a [Channel entity](https://www.pubnub.com/docs/sdks/kotlin/entities/channel) where you provide the name of the channel you want to fire to.

```kotlin
val channel = pubnub.channel("channelName")

channel.fire(
    message: Any,
    meta: Any,
    usePost: Boolean,
).async { result -> /* check result */ }
```

| Parameter | Description |
| --- | --- |
| `message` *Type: `Any`Default: n/a | The payload |
| `meta`Type: `Any`Default: Not set | Metadata object which can be used with the filtering ability |
| `usePost`Type: `Boolean`Default: `false` | Use POST to `fire` |

### Sample code

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}

val pubnub = PubNub.create(configBuilder.build())
val channel = pubnub.channel("myChannel")
val myMessage = JsonObject().apply {
    addProperty("text", "Hello, world")
}

channel.fire(myMessage).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { fireResult ->
        println("Message sent, timetoken: ${fireResult.timetoken}")
    }
}
```

### Response

The `fire()` operation returns a `PNPublishResult?`:

| Parameter | Description |
| --- | --- |
| `timetoken`Type: `Long` | Returns a `Long` representation of the timetoken when the message was published. |

## Signal

##### Available in entities

This method is available to use with the `Channel` entity. For more information, refer to [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel).

The `signal()` function sends a signal to all subscribers of a channel.

###### Prerequisites and limitations

* You must [initialize PubNub](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#configuration) with the `publishKey`.
* You must create a [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel) entity where you will fire to.
* The message payload size (without the URI or headers) is limited to `64` bytes. If you require a larger payload size, [contact support](https://www.pubnub.com/docs/mailto:support@pubnub.com).

###### Signal vs. Message

| **Feature** | **Signals** | **Messages** |
| --- | --- | --- |
| **Payload size** | Limited to 64 bytes (64B) | Up to 32 KiB |
| **Cost efficiency** | Cost less than standard messages | Generally more expensive than signals |
| **Persistence** | Cannot be saved in [Message Persistence](https://www.pubnub.com/docs/sdks/kotlin/api-reference/storage-and-playback) (past signals cannot be accessed) | Can be saved and accessed through Message Persistence |
| **Push Notifications** | Cannot trigger [Mobile Push Notifications](https://www.pubnub.com/docs/sdks/kotlin/api-reference/mobile-push) | Can trigger Mobile Push Notifications |
| **Use case suitability** | Best for non-critical data streams, like geolocation updates | Suitable for critical and non-critical use cases |
| **Metadata support** | Do not support metadata | Support metadata |

:::note Channel separation
Signals and messages should be sent on separate channels to improve connection recovery behaviour.
:::

### Method(s)

```kotlin
val channel = pubnub.channel("myChannel")

channel.signal(
    message: Any,
    customMessageType: String
).async { result -> }
```

| Parameter | Description |
| --- | --- |
| `message` *Type: `Any`Default: n/a | The payload |
| `customMessageType`Type: StringDefault: n/a | A case-sensitive, alphanumeric string from 3 to 50 characters describing the business-specific label or category of the message. Dashes `-` and underscores `_` are allowed. The value cannot start with special characters or the string `pn_` or `pn-`. Examples: `text`, `action`, `poll`. |

### Sample code

```kotlin
val configBuilder = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}
val pubnub = PubNub.create(configBuilder.build())
val channel = pubnub.channel("myChannel")

channel.signal(
    message = "This is a signal!",
    customMessageType = "text-message"
).async { result ->
    result.onFailure { exception ->
        println("Error while publishing")
        exception.printStackTrace()
    }.onSuccess { signalResult ->
        println("Signal sent, timetoken: ${signalResult.timetoken}")
    }
}
```

### Response

The `signal()` operation returns a `PNPublishResult?`:

| Method | Description |
| --- | --- |
| `timetoken`Type: `Long` | Returns a `Long` representation of the timetoken when the signal was published. |

## Subscribe

Subscribe opens a TCP socket and listens for messages and events on a specified entity or set of entities. Set `subscribeKey` during [initialization](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#initialization).

:::tip Conceptual overview
For more general information about subscriptions, refer to [Subscriptions](https://www.pubnub.com/docs/general/channels/subscribe).
:::

Entities are [first-class citizens](https://en.wikipedia.org/wiki/First-class_citizen) that expose their APIs. You can subscribe using the PubNub client or directly on a specific entity:

* [Channel](https://www.pubnub.com/docs/sdks/kotlin/entities/channel)
* [ChannelGroup](https://www.pubnub.com/docs/sdks/kotlin/entities/channel-group)
* [UserMetadata](https://www.pubnub.com/docs/sdks/kotlin/entities/user-metadata)
* [ChannelMetadata](https://www.pubnub.com/docs/sdks/kotlin/entities/channel-metadata)

After `subscribe()`, the client receives new messages. Configure [retryConfiguration](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration) to reconnect automatically after disconnects.

### Subscription scope

Subscriptions let you attach listeners for specific real-time update types. Your app receives messages and events through those listeners. There are two types:

* [Subscription](#create-a-subscription): created from an entity and scoped to that entity (for example, a particular channel)
* [SubscriptionSet](#create-a-subscription-set): created from the PubNub client and scoped to the client (for example, all subscriptions created on a single `pubnub` object). A set can include one or more subscriptions.

The event listener is a single point through which your app receives all the messages, signals, and events in the entities you subscribed to. For information on adding event listeners, refer to [Event listeners](#event-listeners).

### Create a subscription

:::note Managing subscription lifecycle
The `Subscription` object implements the [AutoCloseable](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-auto-closeable/#kotlin.AutoCloseable) interface to help you release resources by unsubscribing and removing all listeners. Always call `Subscription.close()` when you no longer need this `Subscription`.
:::

An entity-level `Subscription` allows you to receive messages and events for only that entity for which it was created. Using multiple entity-level `Subscription`s is useful for handling various message/event types differently in each channel.

```kotlin
// Entity-based, local-scoped

// Specify the channel for subscription
val myChannel = pubnub.channel("channelName")

// Create subscription options, if any
val options = SubscriptionOptions.receivePresenceEvents()

// Return a Subscription object that is used to establish the subscription
val subscription = myChannel.subscription(options)

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

| Parameter | Description |
| --- | --- |
| `options`Type: `SubscriptionOptions` | `Subscription` [behavior configuration](#subscriptionoptions). Use `null` for no specific options. |

### Create a subscription set

:::note Managing subscription lifecycle
The `SubscriptionSet` object implements the [AutoCloseable](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-auto-closeable/#kotlin.AutoCloseable) interface to help you release resources by unsubscribing and removing all listeners. Always call `SubscriptionSet.close()` when you no longer need this `SubscriptionSet`.
:::

A client-level `SubscriptionSet` allows you to receive messages and events for all entities in the set. A single `SubscriptionSet` is useful for similarly handling various message/event types in each channel.

```kotlin
// client-based, general-scoped

pubnub.subscriptionSetOf(
    channels: Set<String>,
    channelGroups: Set<String>,
    options: SubscriptionOptions
)
```

| Parameter | Description |
| --- | --- |
| `> channels`Type: `Set<String>` | Set of channel names to subscribe to. Use an empty set for no channels. |
| `> channelGroups`Type: `Set<String>` | Set of channel group names to subscribe to. Use an empty set for no channel groups. |
| `> options`Type: `SubscriptionOptions` | Additional subscription configuration to define the subscription behavior. If you don't set any options, `EmptyOptions` is used by default. |

:::tip Add/remove sets
You can add and remove subscription sets to create new sets. Refer to the [Other examples](#other-examples-1) section for more information.
:::

#### SubscriptionOptions

`SubscriptionOptions` is a class designed to configure subscription behaviors with optional modifiers. When no specific options are required, `EmptyOptions` is set by default.

The class includes:

| Option | Description |
| --- | --- |
| `receivePresenceEvents()` | Whether presence updates for `userId`s should be delivered through the listener streams. For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel). |
| `filter(predicate: (PNEvent) -> Boolean)` | Allows for custom filtering of events delivered to the subscription based on the provided predicate. Useful for event-specific handling. |

### Method(s)

`Subscription` and `SubscriptionSet` use the same methods to subscribe:

* [Subscribe](#subscribe-1)
* [Subscribe with timetoken](#subscribe-with-timetoken)

#### Subscribe

To subscribe, you can use the following method:

```kotlin
// For subscription
subscription.subscribe()
// For subscription set
subscriptionSet.subscribe()
```

##### Sample code

```kotlin
// Step 1: Create a subscription set
val subscriptionSet = pubnub.subscriptionSetOf(
    // Specify channels with default options
    channels = setOf("my_channel", "other_channel"),
)

// Step 2: Subscribe using the subscription set
subscriptionSet.subscribe()
```

##### Other examples

###### Create a subscription set from 2 individual subscriptions

```kotlin
// Create subscriptions
val subscription1 = pubnub.channel("channelName").subscription()
val subscription2 = pubnub.channelGroup("channelGroup").subscription()
val subscription3 = pubnub.channel("channelName03").subscription()

// Combine into a subscription set
val subscriptionSet = subscription1 + subscription2

// Add another subscription to the set
subscriptionSet += subscription3
// Or
subscriptionSet.add(subscription3)

// Remove a subscription from the set
subscriptionSet -= subscription3
// Or
subscriptionSet.remove(subscription3)
```

##### Returns

The `subscribe()` method doesn't have a return value.

#### Subscribe with timetoken

:::warning Impact on other subscriptions
Subscribing with a timetoken affects all other subscriptions because it overwrites the timetoken in the single PubNub server connection in the SDK. However, those other subscriptions will not deliver messages older than ones that were already delivered - after receiving an event, a subscription only gets future events, ignoring those before or at the time of the last event received.
:::

To subscribe to real-time updates from a given timetoken, use the following method:

```kotlin
subscriptionSet.subscribe(SubscriptionCursor(timetoken = yourTimeToken))
```

| Parameter | Description |
| --- | --- |
| `cursor` *Type: `SubscriptionCursor` | Cursor from which to return any available cached messages. `SubscriptionCursor` would typically include a `timetoken` (long integer) representing the point in time from which to receive updates. |

##### Sample code

```kotlin
subscriptionSet.subscribe(SubscriptionCursor(timetoken = yourTimeToken))
```

##### Returns

The method for subscribing with a timetoken doesn't have a return value.

## Event listeners

Messages and events are received in your app using a listener. This listener allows a single point to receive all messages, signals, and events.

You can attach listeners to the instances of [Subscription](#create-a-subscription), [SubscriptionSet](#create-a-subscription-set), and, in the case of the connection status, the PubNub client.

:::note No built-in event throttling
The PubNub SDK delivers every incoming event to your listener as it arrives — there is no built-in throttling or rate-limiting on the subscriber side. If you need to control how often your application processes events, wrap your listener callback with a throttle or debounce utility from your language or framework ecosystem.
To reduce the number of messages delivered to your client in the first place, use [Subscribe Filters](https://www.pubnub.com/docs/general/channels/subscribe-filters) to filter messages server-side before they reach your listener.
:::

### Add listeners

You can add listeners for various types of updates related to your subscription. You can implement listeners for general updates (that handle multiple event types at once) or choose listeners dedicated to specific event types such as `Message` or `File`.

#### Handle multiple event types

##### Method(s)

```kotlin
fun addListener(listener: EventListener)
```

##### Sample code

```kotlin
// 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()
```

#### Handle one event type

#### Method(s)

You can also directly register listeners for specific event types on the subscription object by assigning lambda expressions. This method allows you to handle events such as messages, signals, message actions, files, objects, and presence.

Using this method, you cannot have multiple listeners attached to the same event type. Assigning a new listener with this method overwrites the previous one.

##### Sample code

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

##### Remove event listener

To remove the listener for a specific event, assign `null` to it.

```kotlin
subscription.onMessage = null
subscription.onSignal = null
subscription.onMessageAction = null
subscription.onFile = null
subscription.onObjects = null
subscription.onPresence = null
```

### Add connection status listener

Use the `StatusListener` interface with your `PubNub` instance to add a listener dedicated to connection status updates.

:::warning Client scope
This listener is only available on the PubNub object.
:::

#### Method(s)

```kotlin
pubnub.addListener(object : StatusListener() {
    override fun status(pubnub: PubNub, status: PNStatus) {
        // Handle connection status updates
        println("Connection Status: ${status.category}")
    }
})
```

#### Sample code

```kotlin
pubnub.addListener(object : StatusListener {
    override fun status(pubnub: PubNub, status: PNStatus) {
        // Handle connection status updates
        println("Connection Status: ${status.category}")
    }
})
```

#### Returns

This method returns the subscription status and will emit various statuses depending on your client network connection.

To help you adjust your app code, see the [Status Events for Subscribe](https://www.pubnub.com/docs/sdks/kotlin/status-events#subscribe) for the exact mapping between the current and deprecated Kotlin SDK statuses.

For more generic information, head to [SDK Connection Lifecycle](https://www.pubnub.com/docs/general/setup/connection-management#sdk-connection-lifecycle).

## Unsubscribe

Stop receiving real-time updates from a [Subscription](#create-a-subscription) or a [SubscriptionSet](#create-a-subscription-set).

### Method(s)

```kotlin
// For subscription
subscription.unsubscribe()
// For subscription set
subscriptionSet.unsubscribe()
```

### Sample code

```kotlin
// Subscribe to a channel
subscription.subscribe()

// Unsubscribe from that channel
subscription.unsubscribe()
```

### Returns

None

## Unsubscribe all

Stop receiving real-time updates from all listeners and remove the entities associated with them.

:::warning Client scope
This method is only available on the PubNub object.
:::

### Method(s)

```kotlin
pubnub.unsubscribeAll()
```

### Sample code

```kotlin
// Subscribe to channels
pubnub.subscribe(channels = listOf("my_channel", "other_channel"))

// Subscribe to a channel group
pubnub.subscribe(channelGroups = listOf("my_channel_group"))

// Later, when you want to unsubscribe from all subscriptions
pubnub.unsubscribeAll()
```

### Returns

None

## Publish (old)

:::warning Not recommended
The use of this method is discouraged. Use [Publish](#publish) instead.
:::

The `publish()` function is used to send a message to all subscribers of a channel. To publish a message you must first specify a valid `publishKey` at initialization. A successfully published message is replicated across the PubNub Real-Time Network and sent simultaneously to all subscribed clients on a channel.

Messages in transit can be secured from potential eavesdroppers with SSL/TLS by setting ssl to true during initialization.

###### Publish anytime

It's not required to be subscribed to a channel in order to publish to that channel.

##### Message data

The message argument can contain any JSON serializable data, including: Objects, Arrays, Ints and Strings. `data` should not contain special classes or functions as these will not serialize. String content can include any single-byte or multi-byte UTF-8 character.

:::warning Don't JSON serialize
It is important to note that you should not JSON serialize when sending signals/messages via PUBNUB. Why? Because the serialization is done for you automatically. Instead, just pass the full object as the message payload. PubNub takes care of everything for you.
:::

###### Message size

The maximum number of characters per message is 32 KiB by default. The maximum message size is based on the final escaped character count, including the channel name. An ideal message size is under 1800 bytes which allows a message to be compressed and sent using single IP datagram (1.5 KiB) providing optimal network performance.

If the message you publish exceeds the configured size, you will receive the following message:

###### Message too large error

```java
["PUBLISHED",[0,"Message Too Large","13524237335750949"]]
```

For further details, check the [support article](https://support.pubnub.com/hc/en-us/articles/360051495932-Calculating-Message-Payload-Size-Before-Publish).

:::tip Need larger messages?
Our platform is optimized for payloads up to 32 KiB. PubNub supports larger messages, but increasing the limit requires a verification of compatibility with your use case.
Talk to [our team](https://www.pubnub.com/company/contact-sales/) to discuss increasing the message size limit for your use case.
:::

###### Message publish rate

Messages can be published as fast as bandwidth conditions will allow. There is a soft limit based on max throughput since messages will be discarded if the subscriber can't keep pace with the publisher.

For example, if 200 messages are published simultaneously before a subscriber has had a chance to receive any messages, the subscriber may not receive the first 100 messages because the message queue has a limit of only 100 messages stored in memory.

###### Publishing to multiple channels

It is not possible to publish a message to multiple channels simultaneously. The message must be published to one channel at a time.

###### Publishing messages reliably

There are some best practices to ensure messages are delivered when publishing to a channel:

* Publish to any given channel in a serial manner (not concurrently).
* Check that the return code is success (for example, `[1,"Sent","136074940..."]`)
* Publish the next message only after receiving a success return code.
* If a failure code is returned (`[0,"blah","<timetoken>"]`), retry the publish.
* Avoid exceeding the in-memory queue's capacity of 100 messages. An overflow situation (aka missed messages) can occur if slow subscribers fail to keep up with the publish pace in a given period of time.
* Throttle publish bursts in accordance with your app's latency needs, for example, Publish no faster than 5 msgs per second to any one channel.

#### Method(s)

To Publish a message you can use the following method(s) in the Kotlin SDK:

```kotlin
pubnub.publish(
    message: Any,
    channel: String,
    shouldStore: Boolean,
    meta: Any,
    queryParam: Map<String, String>,
    usePost: Boolean,
    ttl: Integer
).async { result -> /* check result */ }
```

| Parameter | Description |
| --- | --- |
| `message` *Type: `Any`Default: n/a | The payload |
| `channel` *Type: `String`Default: n/a | Destination of the `message` (channel ID) |
| `shouldStore`Type: `Boolean`Default: account default | Store message in history. If not specified, the decision depends on whether Message Persistence has been enabled for the key or not. |
| `meta`Type: `Any`Default: Not set | Metadata object which can be used with the filtering ability. |
| `queryParam`Type: `Map<String, String>`Default: Not set | One or more query parameters to be passed to the server, for analytics purposes. Overridden in case of conflicts with reserved PubNub parameters, such as UUID or `instance_id`. Accessible from the Admin Portal, and never returned in server responses. |
| `usePost`Type: `Boolean`Default: `false` | Use HTTP POST to `publish`. |
| `ttl`Type: `Integer`Default: n/a | Set a per message time to live in Message Persistence. 1. If `shouldStore = true`, and `ttl = 0`, the message is stored with no expiry time. 2. If `shouldStore = true` and `ttl = X` (`X` is an Integer value), the message is stored with an expiry time of `X` hours unless you have message retention set to `Unlimited` on your keyset configuration in the Admin Portal. 3. If `shouldStore = false`, the `ttl` parameter is ignored. 4. If `ttl` isn't specified, then expiration of the message defaults back to the expiry value for the key. |

#### Sample code

Publish a message to a channel:

```kotlin
pubnub.publish(
    message = JsonObject().apply {
        addProperty("lat", 32L)
        addProperty("lng", 32L)
    },
    channel = "my_channel"
).async { result ->
    result.onSuccess { value ->
        println("Publish timetoken ${value.timetoken}")
    }
}
```

:::note Subscribe to the channel
Before running the above publish example, either using the [Debug Console](https://www.pubnub.com/docs/console/) or in a separate script running in a separate terminal window, [subscribe to the same channel](#subscribe) that is being published to.
:::

#### Returns

The `publish()` operation returns a `PNPublishResult?` which contains the following operations:

| Method | Description |
| --- | --- |
| `timetoken`Type: `Long` | Returns a `Long` representation of the timetoken when the message was published. |

#### Other examples

##### Publish with metadata

```kotlin
pubnub.publish(
    message = mapOf("hello" to "there"),
    channel = "my_channel",
    shouldStore = true,
    meta = mapOf("lang" to "en"),
    usePost = true
).async { result ->
    // check result
}
```

##### Publishing JsonObject (Google GSON)

```kotlin
pubnub.publish(
    message = JsonObject().apply {
        addProperty("lat", 32L)
        addProperty("lng", 32L)
    },
    channel = "my_channel"
).async { result ->
    // check result
}
```

##### Publishing JsonArray (Google GSON)

```kotlin
pubnub.publish(
    message = JsonArray().apply {
        add(32L)
        add(35L)
    },
    channel = "my_channel"
).async { result ->
    // check result
}
```

##### Publishing JSONObject (org.json)

```kotlin
pubnub.publish(
    message = JSONObject().apply {
        put("lat", 32L)
        put("lng", 32L)
    },
    channel = "my_channel"
).async { result ->
    // check result
}
```

##### Publishing JSONArray (org.json)

```kotlin
pubnub.publish(
    message = JSONArray().apply {
        put(32L)
        put(33L)
    },
    channel = "my_channel"
).async { result ->
    // check result
}
```

##### Store the published message for 10 hours

```kotlin
pubnub.publish(
    message = "test",
    channel = "my_channel",
    shouldStore = true,
    ttl = 10
).async { result -> // check result }
```

## Fire (old)

:::warning Not recommended
The use of this method is discouraged. Use [Fire](#fire) instead.
:::

The fire endpoint allows the client to send a message to Functions Event Handlers and [Illuminate](https://www.pubnub.com/docs/illuminate/business-objects/external-data-sources). These messages will go directly to any Event Handlers registered on the channel that you fire to and will trigger their execution. The content of the fired request will be available for processing within the Event Handler. The message sent via `fire()` isn't replicated, and so won't be received by any subscribers to the channel. The message is also not stored in history.

#### Method(s)

To Fire a message you can use the following method(s) in the Kotlin SDK:

```kotlin
pubnub.fire(
    message: Any,
    channel: String,
    meta: Any,
    usePost: Boolean
).async { result -> /* check result */ }
```

| Parameter | Description |
| --- | --- |
| `message` *Type: `Any`Default: n/a | The payload |
| `channel` *Type: `String`Default: n/a | Destination of the `message` (channel ID) |
| `meta`Type: `Any`Default: Not set | Metadata object which can be used with the filtering ability |
| `usePost`Type: `Boolean`Default: `false` | Use POST to `publish` |

#### Sample code

Fire a message to a channel:

```kotlin
pubnub.fire(
    message = listOf("hello", "there"),
    channel = "my_channel"
).async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```

## Signal (old)

:::warning Not recommended
The use of this method is discouraged. Use [Signal](#signal) instead.
:::

The `signal()` function is used to send a signal to all subscribers of a channel.

By default, signals are limited to a message payload size of `64` bytes. This limit applies only to the payload, and not to the URI or headers. If you require a larger payload size, please [contact support](https://www.pubnub.com/docs/mailto:support@pubnub.com).

#### Method(s)

To Signal a message you can use the following method(s) in the Kotlin SDK:

```kotlin
pubnub.signal(
    message: Any,
    channel: String
).async { result -> }
```

| Parameter | Description |
| --- | --- |
| `message` *Type: `Any`Default: n/a | The payload |
| `channel` *Type: `String`Default: n/a | Destination of the `message` (channel ID) |

#### Sample code

Signal a message to a channel:

```kotlin
pubnub.signal(
    message = "hello",
    channel = "my_channel"
).async { result ->
    result.onFailure { exception ->
        // Handle error
    }.onSuccess { value ->
        // Handle successful method result
    }
}
```

#### Response

The `signal()` operation returns a `PNPublishResult?` which contains the following operations:

| Method | Description |
| --- | --- |
| `timetoken`Type: `Long` | Returns a `Long` representation of the timetoken when the signal was published. |

## Subscribe (old)

:::warning Not recommended
The use of this method is discouraged. Use [Subscribe](#subscribe) instead.
:::

#### Receive messages

Your app receives messages and events via event listeners. The event listener is a single point through which your app receives all the messages, signals, and events that are sent in any channel you are subscribed to.

For more information about adding a listener, refer to the [Event Listeners](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#event-listeners) section.

#### Description

This function causes the client to create an open TCP socket to the PubNub Real-Time Network and begin listening for messages on a specified `channel` ID. To subscribe to a `channel` ID the client must send the appropriate `subscribeKey` at initialization.

By default a newly subscribed client will only receive messages published to the channel after the `subscribe()` call completes. If a client gets disconnected from a channel, it can automatically attempt to reconnect to that `channel` ID and retrieve any available messages that were missed during that period. This can be achieved by setting `setReconnectionPolicy` to `PNReconnectionPolicy.LINEAR`, when [initializing](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#initialization) the client.

:::warning Unsubscribing from all channels
**Unsubscribing** from all channels, and then **subscribing** to a new channel Y is not the same as subscribing to channel Y and then unsubscribing from the previously-subscribed channel(s). Unsubscribing from all channels resets the last-received `timetoken` and thus, there could be some gaps in the subscription that may lead to message loss.
:::

#### Method(s)

To Subscribe to a channel you can use the following method(s) in the Kotlin SDK:

```kotlin
pubnub.subscribe(
    channels: List<String>,
    channelGroups: List<String>,
    withTimetoken: Long,
    withPresence: Boolean
)
```

| Parameter | Description |
| --- | --- |
| `channels`Type: `List<String>` | Subscribe to `channels`. Either `channel` ID or `channelGroup` is required. |
| `channelGroups`Type: `List<String>` | Subscribe to `channelGroups`. Either `channel` ID or `channelGroup` is required. |
| `withTimetoken`Type: `Long` | Pass a `timetoken`. |
| `withPresence`Type: `Boolean` | Also subscribe to related presence information. |

#### Sample code

Subscribe to a channel:

```kotlin
pubnub.subscribe(
    channels = listOf("my_channel")
)
```

:::note Event listeners
The response of the call is handled by adding a Listener. Please see the [Listeners section](https://www.pubnub.com/docs/sdks/kotlin#add-event-listeners) for more details. Listeners should be added before calling the method.
:::

#### Returns

:::note PNMessageResult
`PNMessageResult` is returned in the [Listeners](https://www.pubnub.com/docs/sdks/kotlin#add-event-listeners).
:::

The `subscribe()` operation returns a `PNStatus` which contains the following operations:

| Method | Descriptions |
| --- | --- |
| `category`Type: `PNStatusCategory` | See the [Kotlin SDK listener categories](https://www.pubnub.com/docs/sdks/kotlin#add-event-listeners). |
| `error`Type: `Boolean` | Is `true` in case of an error |

The `subscribe()` operation returns a `PNMessageResult` for messages which contains the following operations:

| Method | Descriptions |
| --- | --- |
| `message`Type: `JsonElement` | The message sent on the `channel` ID |
| `subscription`Type: `String?` | The channel group or wildcard subscription match (if exists). |
| `channel`Type: `String` | The `channel` ID for which the message belongs |
| `timetoken`Type: `Long?` | Timetoken for the message |
| `userMetadata`Type: `JsonElement?` | User `metadata` |

The `subscribe()` operation returns a `PNPresenceEventResult` for presence which contains the following operations:

| Method | Descriptions |
| --- | --- |
| `event`Type: `String` | Events like `join`, `leave`, `timeout`, `state-change`, `interval`. |
| `uuid`Type: `String` | `UUID` for the event |
| `timestamp`Type: `Long` | `Timestamp` for the event |
| `occupancy`Type: `Int` | Current `occupancy` |
| `state`Type: `JsonElement?` | `State` of the `UUID` |
| `subscription`Type: `String` | The channel group or wildcard subscription match (if exists) |
| `channel`Type: `String` | The `channel` ID of which the message belongs |
| `timetoken`Type: `Long` | `Timetoken` of the message |
| `userMetadata`Type: `Any` | User `metadata` |

The `subscribe()` operation returns a `PNSignalResult` for signals which contains the following operations:

| Method | Descriptions |
| --- | --- |
| `message`Type: `JsonElement` | The signal sent on the `channel` ID |
| `subscription`Type: `String` | The channel group or wildcard subscription match (if exists) |
| `channel`Type: `String` | The `channel` ID of which the message belongs |
| `timetoken`Type: `Long` | `Timetoken` of the message |
| `userMetadata`Type: `Any` | User `metadata` |

#### Other examples

##### Basic subscribe with logging

```kotlin
val pnConfiguration = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "my_pubkey"
}

val pubnub = PubNub.create(pnConfiguration.build())

pubnub.subscribe(
    channels = listOf("my_channel")
)
```

##### Subscribing to multiple channels

It's possible to subscribe to more than one channel using the [Multiplexing](https://www.pubnub.com/docs/general/channels/subscribe#channel-multiplexing) feature. The example shows how to do that using an array to specify the channel names.

:::note Alternative subscription methods
You can also use [Wildcard Subscribe](https://www.pubnub.com/docs/general/channels/subscribe#wildcard-subscribe) and [Channel Groups](https://www.pubnub.com/docs/general/channels/subscribe#channel-groups) to subscribe to multiple channels at a time. To use these features, the *Stream Controller* add-on must be enabled on your keyset in the [Admin Portal](https://admin.pubnub.com).
:::

```kotlin
pubnub.subscribe(
    channels = listOf("ch1", "ch2") // subscribe to channels information
)
```

##### Subscribing to a Presence channel

:::warning Requires Presence
This method requires that the Presence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel).
:::

For any given channel there is an associated Presence channel. You can subscribe directly to the channel by appending `-pnpres` to the channel name. For example the channel named `my_channel` would have the presence channel named `my_channel-pnpres`. Presence data can be observed inside the `SubscribeCallback#presence(PubNub, PNPresenceResult)` callback.

```kotlin
pubnub.subscribe(
    channels = listOf("my_channel"), // subscribe to channels
    withPresence = true // also subscribe to related presence information
)
```

#### Sample Responses

##### Join event

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "join") {
            pnPresenceEventResult.uuid // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
        }
    }
})
```

##### Leave event

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "leave") {
            pnPresenceEventResult.uuid // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
        }
    }
})
```

##### Timeout event

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "timeout") {
            pnPresenceEventResult.uuid // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
        }
    }
})
```

##### State change event

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "state-change") {
            pnPresenceEventResult.uuid // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
            pnPresenceEventResult.state?.asJsonObject // {"data":{"isTyping":true}}
        }
    }
})
```

##### Interval event

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "interval") {
            pnPresenceEventResult.uuid // 175c2c67-b2a9-470d-8f4b-1db94f90e39e
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
        }
    }
})
```

When a channel is in interval mode with `presence_deltas` `pnconfig` flag enabled, the interval message may also include the following fields which contain an array of changed UUIDs since the last interval message. This settings can be altered in the [Admin Portal](https://admin.pubnub.com).

* joined
* left
* timedout

For example, this interval message indicates there were 2 new UUIDs that joined and 1 timed out UUID since the last interval:

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "interval") {
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
            pnPresenceEventResult.join // ["uuid1", "uuid2"]
            pnPresenceEventResult.timeout // ["uuid3"]
        }
    }
})
```

If the full interval message is greater than `30 KiB` (since the max publish payload is `∼32 KiB`), none of the extra fields will be present. Instead, there will be a `here_now_refresh` Boolean field set to `true`. This indicates to the user that they should do a `hereNow` request to get the complete list of users present in the channel.

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "interval") {
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // # users in channel
            pnPresenceEventResult.hereNowRefresh // true
        }
    }
})
```

##### Wildcard subscribe to channels

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/) (with Enable Wildcard Subscribe checked). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

Wildcard subscribes allow the client to subscribe to multiple channels using wildcard. For example, if you subscribe to `a.*` you will get all messages for `a.b`, `a.c`, `a.x`. The wildcarded `*` portion refers to any portion of the channel string name after the `dot (.)`.

```kotlin
pubnub.subscribe(
    channels = listOf("foo.*") // subscribe to channels information
)
```

:::note Wildcard grants and revokes
Only one level (`a.*`) of wildcarding is supported. If you grant on `*` or `a.b.*`, the grant will treat `*` or `a.b.*` as a single channel named either `*` or `a.b.*`. The same rule applies to revokes - you can revoke permissions with wildcards from one level deep, like `a.*`. However, you can do that only if you initially used wildcards to grant permissions to `a.*`.
:::

##### Subscribing with state

:::warning Requires Presence
This method requires that the Presence add-on is [enabled](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) for your key in the [Admin Portal](https://admin.pubnub.com/). For information on how to receive presence events and what those events are, refer to [Presence Events](https://www.pubnub.com/docs/general/presence/presence-events#subscribe-to-presence-channel).
:::

:::note Required UUID
Always set the `UUID` to uniquely identify the user or device that connects to PubNub. This `UUID` should be persisted, and should remain unchanged for the lifetime of the user or the device. If you don't set the `UUID`, you won't be able to connect to PubNub.
:::

```kotlin
val pnConfiguration = com.pubnub.api.v2.PNConfiguration.builder(UserId("myUserId"), "demo").apply {
    publishKey = "demo"
}

class ComplexData(
    val fieldA: String,
    val fieldB: Int
)

val pubnub = PubNub.create(pnConfiguration.build())

pubnub.addListener(object : SubscribeCallback() {
    override fun status(pubnub: PubNub, status: PNStatus) {
        if (status.category == PNStatusCategory.PNConnectedCategory) {
            pubnub.setPresenceState(
                channels = listOf("awesomeChannel"),
                channelGroups = listOf("awesomeChannelGroup"),
                state = ComplexData("Awesome", 10)
            ).async { result ->
                // handle set state response
            }
        }
    }
})

pubnub.subscribe(
    channels = listOf("awesomeChannel")
)
```

##### Subscribe to a channel group

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```kotlin
pubnub.subscribe(
    channels = listOf("ch1", "ch2"), // subscribe to channels
    channelGroups = listOf("cg1", "cg2"), // subscribe to channel groups
    withTimetoken = 1337L, // optional, pass a timetoken
    withPresence = true // also subscribe to related presence information
)
```

##### Subscribe to the Presence channel of a channel group

:::note Requires Stream Controller and Presence add-ons
This method requires both the *Stream Controller* and *Presence* add-ons are enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```kotlin
pubnub.subscribe(
    channelGroups = listOf("cg1", "cg2"), // subscribe to channel groups
    withTimetoken = 1337L, // optional, pass a timetoken
    withPresence = true // also subscribe to related presence information
)
```

#### Event listeners

You can be notified of connectivity status, message, and presence notifications via the listeners.

Listeners should be added before calling the method.

#### Add listeners

```kotlin
pubnub.addListener(object : SubscribeCallback() {
    override fun status(pubnub: PubNub, status: PNStatus) {
        println("Status category: ${status.category}")
        // PNConnectedCategory, PNReconnectedCategory, PNDisconnectedCategory

        println("Status error: ${status.error}")
        // true or false
    }

    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        println("Presence event: ${pnPresenceEventResult.event}")
        println("Presence channel: ${pnPresenceEventResult.channel}")
        println("Presence uuid: ${pnPresenceEventResult.uuid}")
        println("Presence timetoken: ${pnPresenceEventResult.timetoken}")
        println("Presence occupancy: ${pnPresenceEventResult.occupancy}")
    }

    override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {
        println("Message payload: ${pnMessageResult.message}")
        println("Message channel: ${pnMessageResult.channel}")
        println("Message publisher: ${pnMessageResult.publisher}")
        println("Message timetoken: ${pnMessageResult.timetoken}")
    }

    override fun signal(pubnub: PubNub, pnSignalResult: PNSignalResult) {
        println("Signal payload: ${pnSignalResult.message}")
        println("Signal channel: ${pnSignalResult.channel}")
        println("Signal publisher: ${pnSignalResult.publisher}")
        println("Signal timetoken: ${pnSignalResult.timetoken}")
    }

    override fun messageAction(pubnub: PubNub, pnMessageActionResult: PNMessageActionResult) {
        with(pnMessageActionResult.messageAction) {
            println("Message reaction type: $type")
            println("Message reaction value: $value")
            println("Message reaction uuid: $uuid")
            println("Message reaction actionTimetoken: $actionTimetoken")
            println("Message reaction messageTimetoken: $messageTimetoken")
        }

        println("Message reaction subscription: ${pnMessageActionResult.subscription}")
        println("Message reaction channel: ${pnMessageActionResult.channel}")
        println("Message reaction timetoken: ${pnMessageActionResult.timetoken}")
    }

    override fun objects(pubnub: PubNub, objectEvent: PNObjectEventResult) {
        println("Object event channel: ${objectEvent.channel}")
        println("Object event publisher: ${objectEvent.publisher}")
        println("Object event subscription: ${objectEvent.subscription}")
        println("Object event timetoken: ${objectEvent.timetoken}")
        println("Object event userMetadata: ${objectEvent.userMetadata}")

        with(objectEvent.extractedMessage) {
            println("Object event event: $event")
            println("Object event source: $source")
            println("Object event type: $type")
            println("Object event version: $version")
        }
    }
})
```

#### Remove listeners

```kotlin
val listener = object : SubscribeCallback() {
    override fun status(pubnub: PubNub, pnStatus: PNStatus) {}

    override fun message(pubnub: PubNub, pnMessageResult: PNMessageResult) {}
    // and other callbacks
}

pubnub.addListener(listener)

// some time later
pubnub.removeListener(listener)
```

#### Handling disconnects

The client may disconnect due to unpredictable network conditions.

You can configure automatic reconnection with the [retryConfiguration](https://www.pubnub.com/docs/sdks/kotlin/api-reference/configuration#configuration) parameter.

#### Listeners status events

Refer to [Status Events for Subscribe](https://www.pubnub.com/docs/sdks/kotlin/status-events#subscribe) for details.

## Unsubscribe (old)

:::warning Not recommended
The use of this method is discouraged. Use [Unsubscribe](#unsubscribe) instead.
:::

When subscribed to a single channel, this function causes the client to issue a `leave` from the `channel` and close any open socket to the PubNub Network. For multiplexed channels, the specified `channel`(s) will be removed and the socket remains open until there are no more channels remaining in the list.

:::warning Unsubscribing from all channels
**Unsubscribing** from all channels, and then **subscribing** to a new channel Y is not the same as subscribing to channel Y and then unsubscribing from the previously-subscribed channel(s). Unsubscribing from all channels resets the last-received `timetoken` and thus, there could be some gaps in the subscription that may lead to message loss.
:::

#### Method(s)

To `Unsubscribe from a channel` you can use the following method(s) in the Kotlin SDK:

```kotlin
pubnub.unsubscribe(
    channels: List<String>,
    channelGroups: List<String>
)
```

| Parameter | Description |
| --- | --- |
| `channels`Type: `List<String>` | Unsubscribe from `channels`. Either `channel` ID or `channelGroup` is required. |
| `channelGroups`Type: `List<String>` | Unsubscribe from `channelGroups`. Either `channel` ID or `channelGroup` is required`. |

#### Sample code

Unsubscribe from a channel:

```kotlin
pubnub.unsubscribe(
    channels = listOf("my_channel") // subscribe to channel groups
)
```

:::note Event listeners
The response of the call is handled by adding a Listener. Please see the [Listeners section](https://www.pubnub.com/docs/sdks/kotlin#add-event-listeners) for more details. Listeners should be added before calling the method.
:::

#### Response

The output below demonstrates the response to a successful call:

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "leave") {
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // 2
            pnPresenceEventResult.uuid // left_uuid
        }
    }
})
```

#### Other examples

##### Unsubscribing from multiple channels

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```kotlin
pubnub.unsubscribe(
    channels = listOf("ch1", "ch2", "ch3"),
    channelGroups = listOf("cg1", "cg2", "cg3")
)
```

Example response:

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "leave") {
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // 2
            pnPresenceEventResult.uuid // left_uuid
        }
    }
})
```

##### Unsubscribing from a channel group

:::note Requires Stream Controller add-on
This method requires that the *Stream Controller* add-on is enabled for your key in the [Admin Portal](https://admin.pubnub.com/). Read the [support page](https://support.pubnub.com/hc/en-us/articles/360051974791-How-do-I-enable-add-on-features-for-my-keys-) on enabling add-on features on your keys.
:::

```kotlin
pubnub.unsubscribe(
    channelGroups = listOf("cg1", "cg2", "cg3")
)
```

Example response:

```kotlin
pubnub.addListener(object : EventListener {
    override fun presence(pubnub: PubNub, pnPresenceEventResult: PNPresenceEventResult) {
        if (pnPresenceEventResult.event == "leave") {
            pnPresenceEventResult.timestamp // 1345546797
            pnPresenceEventResult.occupancy // 2
            pnPresenceEventResult.uuid // left_uuid
        }
    }
})
```

## Terms in this document

* **Access Manager** - A cryptographic, token-based permission administrator that allows you to regulate clients' access to PubNub resources, such as channels, channel groups, and user IDs.
* **Action** - The type of activity (procedure) to execute when a condition is satisfied (for example, sending a message).
* **Billing alert notification** - A means of informing a user that a billing alert has been triggered. Before notifications can happen, a billing alert must be triggered first.
* **Business Object** - A container for data fields and metrics that defines aggregations and data sources.
* **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.
* **Channel pattern** - A way to group and analyze channel data to track performance metrics like message counts and user engagement over time with PubNub Insights.
* **Condition** - A requirement that must be satisfied or evaluated to true for an action to be executed. Input in a decision table.
* **Cryptor** - An implementation of a specific cryptographic algorithm used for data encryption/decryption that adheres to a standard interface.
* **Dashboard** - A collection of widgets (charts) that give an overview of the metrics one is evaluating.
* **Data fields** - Data you want Illuminate to track. These can be quantitative (measures), like "Number" or "Timestamp" or qualitative (dimensions) values, like "String" that can be used to categorize and segment data. Data fields can be aggregated and calculated.
* **Decision** - A collection (or decision table) of conditions and actions. When conditions are satisfied, the corresponding actions are triggered as per defined rules.
* **End Customer** - A customer of a PubNub partner. End customers do not have direct access to the Admin Portal. Instead, they interact with PubNub products—such as Illuminate—through the partner’s portal, where PubNub services are embedded. They can create PubNub objects only within this partner-provided environment.
* **Entity** - A subscribable object within a PubNub SDK that allows you to perform context-specific operations.
* **Listener** - A function or objectthat reacts to events or messages, like new chat messages or connection updates, letting your app respond in real-time.
* **Mapped/Unmapped** - Whether the data source for a data field has been defined or the action has been configured.
* **MCP Server** - A Model Context Protocol server that coordinates communication and synchronization between AI agents, clients, or services, such as Cursor IDE and Windsurf.
* **Message** - A unit of data transmitted between clients or between a client and a server in PubNub, containing information such as text, binary data, or structured data formats like JSON. Messages are sent over channels and can be tracked for delivery and read status.
* **Metric** - What exactly is evaluated using measures and dimensions (collectively called data fields), as well as aggregation functions.
* **Module** - A Functions v1 container that groups related functions for configuration and deployment on an app’s keysets.
* **Origin** - The subdomain used to establish a connection to the PubNub network that allows your application's traffic to appear like it's coming from your own domain.
* **Package** - A Functions v2 container that groups Functions, tracks Revisions, and is deployed to keysets.
* **Partner** - A PubNub customer who resells PubNub products, such as Illuminate, to their own customers. Partners have access to the Admin Portal, enabling them to create and manage PubNub objects for themselves or on behalf of their end customers.
* **Publish Key** - A unique identifier that allows your application to send messages to PubNub channels. It's part of your app's credentials and should be kept secure.
* **PubNub** - PubNub is a real-time messaging platform that provides APIs and SDKs for building scalable applications. It handles the complex infrastructure of real-time communication, including: Message delivery and persistence, Presence detection, Access control, Push notifications, File sharing, Serverless processing with Functions and Events & Actions, Analytics and monitoring with BizOps Workspace, AI-powered insights with Illuminate.
* **Push token** - A device identifier issued by a push provider (APNs or FCM) used to register a device for receiving mobile push notifications.
* **Rule** - A definition (row in a decision table) stating which action should be triggered for which condition.
* **Service Integration** - A machine identity that represents a program or service consuming the Admin API, scoped to your account and authenticated using expirable API keys with configurable permissions.
* **Signal** - A non-persistent message limited to 64 bytes designed for high-volume usecases where the the most recent data is relevant, like GPS location updates.
* **Subscribe Key** - A unique identifier that allows your application to receive messages from PubNub channels. It's part of your app's credentials and should be kept secure.
* **Timetoken** - A unique identifier for each message that represents the number of 100-nanosecond intervals since January 1, 1970, for example, 16200000000000000.
* **Trigger details** - A set of predefined criteria for a given billing alert. When met, billing alert notifications are generated.
* **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.
* **User ID** - UTF-8 encoded, unique string of up to 92 characters used to identify a single client (end user, device, or server) that connects to PubNub.
* **Vibe Coding** - A way to build applications in an intuitive, relaxed, and improvisational manner, using AI tools and natural language descriptions.