Initial configuration
Before building your chat app, you must initialize and configure the Chat SDK.
Start by signing into the Admin Portal or creating an account if you don't have one yet.
Then, create an app on the Admin Portal. You will need a PubNub app to get a keyset that consists of a Subscribe Key and a Publish Key. These keys will let you establish a connection between PubNub and the chat app you're going to create with the Chat SDK.
Limit of 3 keysets for Free tier accounts
Effective February 3, 2025, all Free tier accounts will be limited to a maximum of three keysets. If your account exceeds this limit, you must delete existing keysets to create new ones.
When you create a new app on the Admin Portal, the first set of demo keys is generated automatically, but a single app can have as many keysets as you like. We recommend that you create separate keysets for production and test environments.
Enable features on your keyset
Each keyset has its own configuration settings in the Admin Portal. To use some features in your chat app, you must enable appropriate settings on your app's keyset on the Admin Portal.
To use the Chat SDK, create or update users, track presence, and store messages in history, you must have App Context, Presence, and Message Persistence enabled on your keyset.
Download the SDK
Download the SDK from any of the following sources:
Use Maven
To integrate PubNub into your project using Maven, add the following dependency in your pom.xml:
1<dependency>
2   <groupId>com.pubnub</groupId>
3   <artifactId>pubnub-chat</artifactId>
4   <version>0.15.2</version>
5</dependency>
Use Gradle
To integrate PubNub into your project using Gradle (including Android Studio), add the following dependency in your build.gradle file:
1implementation ("com.pubnub:pubnub-chat:0.15.2")
Get the source code
https://github.com/pubnub/kmp-chat
Initialize PubNub
Once you have a PubNub account and an app created on the Admin Portal, you can start initializing PubNub Client API context and establish account-level credentials.
To initialize PubNub with the Chat SDK, use the init() method.
You must provide at least these two parameters: subscribeKey, or userId. Apart from the required parameters, you can also configure additional options when initializing the Chat SDK. These options will let you add configuration required to implement advanced chat features, like typing indicator, user offline/online presence, push notifications, or client-side limiting that prevents spamming.
The init() method takes the following parameters:
1Chat.init(chatConfig: ChatConfiguration, pnConfiguration: PNConfiguration)
2
3// Chat SDK-specific configuration
4interface ChatConfiguration {
5    val logLevel: LogLevel
6    val typingTimeout: Duration
7    val storeUserActivityInterval: Duration
8    val storeUserActivityTimestamps: Boolean
9    val pushNotifications: PushNotificationsConfig {
10      val sendPushes: Boolean,
11      val deviceToken: String?,
12      val deviceGateway: PNPushType,
13      val apnsTopic: String?,
14      val apnsEnvironment: PNPushEnvironment
15    }
Input parameters
| Parameter | Feature | Description | 
|---|---|---|
| logLevelType:  LogLevelDefault: OFF | Error logging | Specifies if any Chat SDK-related errors should be logged. It's disabled by default. Available options include: OFF,ERROR,WARN,INFO,DEBUG, andVERBOSE. | 
| typingTimeoutType:  DurationDefault: 5.seconds | Typing Indicator | Specifies the default timeout after which the typing indicator automatically stops when no typing signals are received. The default value is set to 5 seconds. Minimal to 1 seconds. | 
| storeUserActivityIntervalType:  DurationDefault: 60.seconds | User's last online activity, global presence | Specifies how often the user global presence in the app should be updated. Requires storeUserActivityTimestampsto be set totrue. The default value is set to 60 seconds, and that's the minimum possible value. If you try to set it to a lower value, you'll get thestoreUserActivityInterval must be at least 60000mserror. | 
| storeUserActivityTimestampsType:  BooleanDefault: false | User's last online activity, global presence | Specifies if you want to track the user's global presence in your chat app. The user's activity is tracked through the lastActiveTimestampparameter on theUserobject. | 
| pushNotificationsType:  PushNotificationsConfigDefault: n/a | Push Notifications | List of parameters you must set if you want to enable sending/receiving mobile push notifications for phone devices, either through Apple Push Notification service (APNS) or Firebase Cloud Messaging (FCM). | 
| → sendPushesType:  BooleanDefault: false | as above | The main option for enabling sending notifications. It must be set to trueif you want a particular client (whether a mobile device, web browser, or server) to send push notifications to mobile devices.These push notifications are messages with a provider-specific payload that the Chat SDK automatically attaches to every message. Chat SDK includes a default payload setup for deviceGatewayin every message sent to the registered channels.This is the only required option to enable if you want to send push notifications to Android devices. For iOS devices, you also have to configure apnsTopic. | 
| → deviceTokenType:  StringDefault: n/a | as above | Option for receiving notifications on iOS and Android devices. A device token refers to the unique identifier assigned to a specific mobile device by a platform's push notification service. It targets and delivers push notifications to the intended app on that specific device. Suppose you don't set this option and try to run channel registration-related methods. In that case, you'll get the Device Token has to be defined in Chat pushNotifications configerror.Refer to the official Apple and Google docs to learn how to obtain a device token for the APNs and FCM services. | 
| → deviceGatewayType:  PNPushTypeDefault: FCM | as above | Option for receiving push notifications on Android ( fcm) or iOS (apnsorapns2) devices. These are the available types:
 | 
| → apnsTopicType:  StringDefault: n/a | as above | An Apple specific-option for sending and receiving notifications. This string is a bundle ID that you must define yourself for your iOS app so that Apple could enable push notifications for it in APNs. The string takes the following format: com.domainname.applicationname. Apple combines that ID with your Team ID (generated by Apple) and creates an App ID for your application.To send pushes from an iOS device, you must also set sendPushestotrue. To receive pushes on an iOS device, you must also setdeviceGatewaytoapns2, definedeviceToken, andapnsEnvironment. Suppose you don't configureapnsTopic, but setdeviceGatewaytoapns2. In that case, you'll get theapnsTopic has to be defined when deviceGateway is set to apns2error and Chat SDK won't attach theapnspayload to messages. | 
| → apnsEnvironmentType:  PNPushEnvironmentDefault: DEVELOPMENT | as above | Option for receiving notifications on iOS devices. When registering for push notifications, this option specifies whether to use the development ( DEVELOPMENT) or production (PRODUCTION) APNs environment. | 
| rateLimitFactorType:  IntDefault: 2 | Client-side rate limiting | The so-called "exponential backoff" which multiplicatively decreases the rate at which messages are published on channels. It's bound to the rateLimitPerChannelparameter and is meant to prevent message spamming caused by excessive retries.The default value of 2means that if you setrateLimitPerChannelfor direct channels to 1 second and try to send three messages on such a channel type within the span of one second, the second message will be published one second after the first one (just like therateLimitPerChannelvalue states), but the third one will be published two seconds after the second one, meaning the publishing time is multiplied by2. | 
| rateLimitPerChannelType:  Map<ChannelType, Duration>Default: n/a | Client-side rate limiting | Client-side limit that states the rate at which messages can be published on a given channel type. Its purpose is to prevent message spamming in your chat app. This parameter takes an object with these three parameters: direct,group, andpublic. For example, if you decide that messages on alldirectchannels must be published no more often than every second, this is how you set it:ChatConfiguration(rateLimitPerChannel = RateLimitPerChannel(direct = 1.seconds)). | 
| → directType:  DurationDefault: 0(no limit) | as above | Rate set on all direct (1:1) channels at which messages can be published. | 
| → groupType:  DurationDefault: 0(no limit) | as above | Rate set on all group channels at which messages can be published. | 
| → publicType:  DurationDefault: 0(no limit) | as above | Rate set on all public channels at which messages can be published. | 
| → unknownType:  DurationDefault: 0(no limit) | as above | Rate set on all channels created using the Kotlin SDK instead of Kotlin Chat SDK. | 
| customPayloadsType:  CustomPayloadsDefault: n/a | Send and receive messages | Property that lets you define your custom message payload to be sent and/or received by Chat SDK on one or all channels, whenever it differs from the default message.textChat SDK payload.It also lets you configure your own message actions whenever a message is edited or deleted. For examples, check Custom payload. | 
| → getMessagePublishBodyType: Function that takes two parameters:  
 Default: n/a | as above | Function that lets Chat SDK send your custom payload structure. It defines the structure of your own message payload's body (of anytype) that you're sending through PubNub.Expand the Message-related types section for more details on the required TextMessageContentstructure.Define getMessageResponseBodywhenever you usegetMessagePublishBody. | 
| → getMessageResponseBodyType: Function that takes a  JsonElement.Default: n/a | as above | Function that lets Chat SDK receive your custom payload structure. Use it to let Chat SDK translate your custom message payload into the default Chat SDK message format (defined in TextMessageContent).Expand the Message-related types section for more details on the required TextMessageContentstructure.Define getMessagePublishBodywhenever you usegetMessageResponseBody. | 
| → editMessageActionNameType:  StringDefault: n/a | as above | A type of action you want to be added to your Message object whenever a published message is edited, like "changed"or"modified".The default message reaction used by Chat SDK is "edited".Expand the Message-related types section for more details. | 
| → deleteMessageActionNameType:  StringDefault: n/a | as above | A type of action you want to be added to your Message object whenever a published message is deleted, like "removed".The default message reaction used by Chat SDK is "deleted".Expand the Message-related types section for more details. | 
| → reactionsActionNameType:  StringDefault: n/a | as above | A type of action you want to be added to your Message object whenever a reaction is added to a published message, like "reacted".The default message reaction used by Chat SDK is "reactions".Expand the Message-related types section for more details. | 
| syncMutedUsersType:  BooleanDefault: false | User moderation | Whether the mute list is synchronized across sessions and devices. For more information, refer to Sync muted users. | 
| userId*Type:  StringDefault: n/a | n/a | Unique User ID that becomes your app's current user. It's a string of up to 92 characters that identifies a single client (end user, device, or server) that connects to PubNub. Based on User ID, PubNub calculates pricing for your apps' usage. User ID should be persisted and remain unchanged. If you don't set userId, you won't be able to connect to PubNub. | 
| subscribeKey*Type:  StringDefault: n/a | Receive messages | Specifies the key used to subscribe to a channel. | 
| → publishKeyType:  StringDefault: n/a | Send messages | Specifies the key used to publish messages on a channel. | 
Sync muted users
The syncMutedUsers parameter determines whether the mute list is synchronized across sessions and devices using a specific App Context User object.
When set to false, the mute list modifications are stored only for the duration of the current session. Once the session ends, the mute list is cleared.
When set to true, the client-side mute list is automatically saved and retrieved from App Context, ensuring that the muted user list persists beyond the current session. App Context uses a designated channel where all mute list data is sent: PN_PRV.$currentUserId.mute1.
Mute list and Access Manager
If you use Access Manager within your chat app and syncMutedUsers is enabled, you must grant the Chat SDK user the following permissions:
- readpermission to the- PN_PRV.$currentUserId.mute1channel.
- update,- delete, and- getpermissions for the- PN_PRV.$currentUserId.mute1user.
Make sure to change $currentUserId to the user ID of the chat user that will use the mute list functionality.
Additional configuration options
Since the Chat SDK heavily relies on the latest Kotlin SDK for all the underlying methods, when initializing the Chat SDK client, you can also make use of all optional parameters that come with the Kotlin SDK.
For example, you may want to use Access Manager and initialize the Chat SDK with secretKey (in the server-side code) or token inside PNConfiguration that is passed to the Chat.init() method (in the client-side code). You can also decide how long the server will consider the client alive for presence (presenceTimeout) or how often the client will announce itself to the server (heartbeatInterval).
For the whole list of all such inherited optional parameters which you can define when initializing the Chat SDK instance, check the Kotlin SDK configuration document.
Output parameters
| Type | Description | 
|---|---|
| PNFuture<Chat> | Object returning a new PubNub chat instance. | 
Sample code
Required setup
Use this basic example to initialize the client setting only the required parameters.
1// initialize your Chat SDK client using your app keys from the Admin Portal and a unique user ID for your client that you'll come up with
2val chatConfig = ChatConfiguration()
3val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"), subscribeKey = "mySubscribeKey").build()
4
5Chat.init(chatConfig, pnConfiguration).async { result ->
6    result.onSuccess { chat: Chat ->
7        println("Chat successfully initialized")
8    }.onFailure { exception: PubNubException ->
9        println("Exception initialising chat: ${exception.message}")
10    }
11}
Access Manager token provided
Use this basic example to initialize the client chat when Access Manager is enabled. Token should be generated by a chat or core SDK instance initialized with a secretKey.
1// initialize your Chat SDK client using your app keys from the Admin Portal and a unique user ID for your client that you'll come up with
2val chatConfig = ChatConfiguration()
3val userIdValue = "clientAppUserId"
4val clientChat: Chat
5
6val token = serverChat.pubNub.grantToken(
7    ttl = 100,
8    channels = listOf(ChannelGrant.name(get = true, name = "anyChannelForNow")),
9    uuids = listOf(UUIDGrant.id(id = userIdValue, get = true, update = true))
10).await().token
11
12val pnConfiguration = PNConfiguration.builder(userId = UserId(userIdValue), subscribeKey = "mySubscribeKey").apply {
13    authToken = token
14}.build()
15
When a token expires, or a new token is needed because of changes in permissions, you must generate a new token on the server side and set it on the client side.
1clientChat.pubNub.setToken(newToken)
Typing indicator timeout
Initialize the PubNub Chat SDK and set the default typing indicator timeout value to three seconds.
1val chatConfig = ChatConfiguration(typingTimeout = 3.seconds)
2val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"), subscribeKey = "mySubscribeKey").build()
3
4Chat.init(chatConfig, pnConfiguration).async { result ->
5    result.onSuccess { chat: Chat ->
6        println("Chat successfully initialized")
7    }.onFailure { exception: PubNubException ->
8        println("Exception initialising chat: ${exception.message}")
9    }
10}
Client-side rate limiting
Initialize the PubNub Chat SDK and set the hard limit for message publishing on public channels to three seconds. If there are more publish retries within this limit, each next retry limit should be multiplied by 3.
1val chatConfig = ChatConfiguration(rateLimitFactor = 3, rateLimitPerChannel(public = 3.seconds))
2val pnConfiguration = PNConfiguration.builder(userId = UserId("myUserId"), subscribeKey = "mySubscribeKey").build()
3
4Chat.init(chatConfig, pnConfiguration).async { result ->
5    result.onSuccess { chat: Chat ->
6        println("Chat successfully initialized")
7    }.onFailure { exception: PubNubException ->
8        println("Exception initialising chat: ${exception.message}")
9    }
10}
Custom payload
When initializing Chat SDK, you can pass your custom message payload structure using the customPayloads object and related properties. This will let Chat SDK correctly interpret your app's messages when sending and receiving them.
Define custom payload for all channels
Let's say your app doesn't follow the default message.text message body structure imposed by Chat SDK but instead uses the my.custom.payload.structure.text structure.
To successfully communicate with PubNub and send/receive messages through Chat SDK, pass your custom payload to all channels. Additionally, define your custom action names to be added to messages when they're edited or deleted.
1ChatConfiguration(
2    customPayloads = CustomPayloads(
3        getMessagePublishBody = { content, _, _ ->
4            mapOf(
5                "custom" to mapOf(
6                    "payload" to mapOf(
7                        "text" to content.text
8                    )
9                ),
10                // optionally also save files and type: "files" to content.files
11            )
12        },
13        getMessageResponseBody = { json: JsonElement, _, _ ->
14            EventContent.TextMessageContent(
15                json.asJsonObject?.getAsJsonObject("custom")?.getAsJsonObject("payload")?.getAsJsonObject("text")?.asString ?: error("Message cannot be parsed"),
Define custom payload for one channel
Let's say your app doesn't follow the default message.text message body structure imposed by Chat SDK for support-channel but instead uses the my.custom.payload.structure.text structure to communicate with PubNub.
Pass your custom payload to support-channel to successfully communicate with PubNub and send/receive messages through Chat SDK. Additionally, define your custom action names to be added to messages when they're edited or deleted.
The code sets up a PubNub chat instance with specific handlers for processing message payloads differently based on the channel.
1var chat: Chat
2val customPayloads: CustomPayloads = CustomPayloads(
3    getMessagePublishBody = { content, channelId, defaultMessagePublishBody ->
4        // Define which channel should use custom payload
5        if (channelId == "support-channel") {
6            mapOf(
7                "custom" to mapOf(
8                    "payload" to mapOf(
9                        "text" to content.text
10                    )
11                ),
12                "files" to content.files
13            )
14        } else {
15            // The rest of the channels will use the default Chat SDK message body structure
Next steps
Now that you've initialized and configured the Chat SDK, you can start creating channels, adding users, and powering your app with all sorts of features.