Real-time chat

Real-time chat keeps fans and users connected, enhancing the experience with instantaneous communication. By the end of this document, you will:

  • Understand how to implement a real-time chat
  • Learn how to create and manage channels efficiently
  • Enhance engagement with user mentions and message reactions
  • Discover how to catch up on past conversations
Cross-platform support

While this guide showcases JavaScript code examples from our web application, PubNub also offers dedicated Chat SDKs for Android and iOS with equivalent functionality. For platforms without a dedicated Chat SDK, all these features can be implemented using PubNub's core SDKs which are available for numerous programming languages and platforms. The concepts and features demonstrated here apply across all implementations, regardless of your target platform.

How PubNub helps

Role in the solutionPubNub feature (click to learn more!)
  • Create and manage channels
  • Send and receive messages
Pub/Sub
  • Detect online users
  • Show online status
Presence
  • Catch up on past messages
Message Persistence
  • Store user profiles and metadata
  • Track user scores and preferences
  • Store channel metadata and attributes
App Context
  • Add emoji reactions to messages
  • Track reaction counts
Message Reactions
  • Tag and notify users in messages
User Mentions
  • Authorize users
  • Control channel permissions
Access Manager

Use case overview

The Live Events demo highlights how PubNub's real-time chat creates engaging shared experiences for live sports viewers.

Live Stream Chat

This Live Events chat demo shows these core PubNub features:

  • Real-time messaging - Instant delivery of chat messages and reactions (< 30ms within a single PubNub region)
  • User management - Complete profiles with rich metadata stored in App Context
  • Channel management - Public chat rooms with metadata and organization
  • Message history - Access to previous messages for users joining late
  • User presence - Real-time tracking of who's online
  • Message reactions - Emoji reactions to messages for lightweight engagement
  • User mentions - Direct references to other users within messages
  • Authorization - Token-based access control using PubNub Access Manager

Basic setup for chat

The Live Events demo implements chat functionality using PubNub's JavaScript Chat SDK. This SDK provides higher-level abstractions specifically designed for chat applications, building on PubNub's core real-time infrastructure.

// Initialize the Chat SDK
async function initializeChat(userId) {
// Get secure token from Access Manager server
const { accessManagerToken } = await getAuthKey(userId);

// Initialize the Chat SDK with user credentials and features
const chat = await Chat.init({
// Your unique publish key from the PubNub Admin Portal
publishKey: process.env.NEXT_PUBLIC_PUBNUB_PUBLISH_KEY,

// Your unique subscribe key from the PubNub Admin Portal
subscribeKey: process.env.NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY,

// The unique identifier for this user - determines identity and permissions
userId: userId,
show all 22 lines
Global vs. channel presence

The Live Events demo uses channel-based presence to track who's online in specific channels, which is ideal for room-based applications. PubNub also offers global presence tracking with the storeUserActivityTimestamps and storeUserActivityInterval parameters, which monitors user activity across all channels. Global presence is useful for applications that need user status indicators (like "last seen") regardless of which channel a user is in.

This initialization is the foundation for all chat functionality in the demo. It sets up the Chat SDK (@pubnub/chat) with the necessary credentials and features, including user activity tracking.

icon

Required Admin Portal config


Real-time messaging

Real-time messaging forms the core of PubNub's platform, delivering messages in under 30ms within a single PubNub region.

At its foundation, PubNub operates a global network of data centers that manage message delivery through persistent socket connections. When a user sends a message, it travels to the nearest PubNub edge server, is replicated across the network and delivered to all subscribers on that channel.

The Live Events demo uses the JavaScript Chat SDK, which provides intuitive, chat-specific methods that abstract away the complexity of PubNub's core publish/subscribe operations.

The demo implements two key messaging operations: sending and receiving messages.

Sending and receiving messages

When a user sends a message, PubNub broadcasts it to all channel subscribers within milliseconds while simultaneously storing it for future retrieval.

icon

Under the hood

Send messages

To send messages, the demo uses the Chat SDK's MessageDraftV2 API, which provides rich message composition capabilities including support for mentions and formatting:

// Define a ref to store the message draft
const messageDraftRef = useRef(null); // In TypeScript: useRef<MessageDraftV2 | null>(null)

// Initialize a message draft when the chat component mounts
useEffect(() => {
// MessageDraftV2 maintains the state of message being composed
// with special handling for mentions and other rich content
messageDraftRef.current = channel.createMessageDraftV2({
userSuggestionSource: 'channel' // Suggest users from current channel
});

return () => {
// Cleanup message draft when component unmounts
messageDraftRef.current = null;
}
show all 49 lines

This approach enables richer messages than the simpler sendText() method, supporting features like:

  1. User mentions - Automatically detecting when users type @ to suggest mentions
  2. Structured content elements - Handling specialized elements like links and references beyond plain text
  3. Message composition state - Maintaining draft state during typing

Behind the scenes, messageDraft.send() publishes the message to PubNub's global network with all metadata and formatting intact, delivering it to all subscribers in under 30ms within a single PubNub region

Receive messages

For receiving messages, the demo uses the connect() method to establish a real-time connection.

// Define state to store messages
const [messages, setMessages] = useState([]); // In TypeScript: useState<Message[]>([])

// Connect to a channel to receive real-time messages
const setupMessageReceiver = async () => {
const channel = await chat.getChannel(activeChannelId);

// connect() establishes a connection to PubNub's network using HTTP long polling,
// sets up filtered message listeners, and handles reconnection automatically.
// Messages arrive as they're published by other users.
const unsubscribe = channel.connect((message) => {
setMessages(prevMessages => [...prevMessages, message]);
});

return unsubscribe; // Cleanup function that properly terminates the connection
show all 16 lines

This implementation creates a seamless chat experience where messages flow instantly between participants, creating the feeling of a live conversation.

Message timestamps

The Game Chat panel displays each message with sender information and timestamp.

PubNub automatically assigns each message a unique "timetoken" when it's published - a high-precision timestamp (microsecond-level accuracy) that's used for message ordering and timestamp display. The Chat SDK provides utilities to convert these timetokens into readable formats.

Here's how the Live Events demo displays message timestamps:

// Inside ChatMessage.tsx component
import { Message, TimetokenUtils } from '@pubnub/chat'

// Convert PubNub's timetoken to readable hours:minutes format
function pubnubTimetokenToHHMM(timetoken) {
// PubNub magic: TimetokenUtils converts PubNub's 17-digit timetoken
// (microseconds since epoch) into a JavaScript Date object
const date = TimetokenUtils.timetokenToDate(timetoken)

// Format as HH:MM with leading zeros
return `${(date.getHours() + '').padStart(2, '0')}:${(
date.getMinutes() + ''
).padStart(2, '0')}`
}

show all 27 lines

PubNub's timetoken system ensures consistent timestamps across all clients regardless of timezone or local clock accuracy, since the timestamp is assigned by PubNub's servers when the message is published, not by the client's clock.

The TimetokenUtils.timetokenToDate() method converts this UTC timetoken to a JavaScript Date object, which automatically adjusts to display in the user's local timezone. This means the HH:MM format shown will reflect the correct local time for each user, regardless of where they are located.

Channel management

Channels are the fundamental building blocks of PubNub's communication infrastructure. Think of channels as dedicated pathways or rooms where messages flow between connected clients.

The Live Events demo uses a focused approach with a single public Game Chat channel where all viewers discuss the ongoing sports event.

Game Chat channel

This creates a virtual stadium effect where everyone experiences the same conversation.

icon

Under the hood

Create a channel

PubNub's core platform uses a streamlined approach to channels—they simply exist when you start publishing or subscribing to them, with no formal creation process required.

The JavaScript Chat SDK enhances this by using PubNub's App Context to store rich channel metadata (name, description, avatar) and organize channels by type (public, group, private). This metadata persists in PubNub's infrastructure, making it available to all clients.

JavaScript Chat SDK provides methods to formally create, manage, and remove channels with associated App Context metadata. While the demo performs these operations client-side for simplicity, in production applications these channel management functions would typically be handled by server-side code or specialized admin clients rather than by regular event participants.

The demo initializes a public channel with such descriptive metadata:

// Create the public channel with metadata
const gameChannel = await chat.createPublicConversation({
channelId: "game.chat",
channelData: {
name: "Game Chat",
description: "Public conversation about the game",
custom: {
profileUrl: "https://example.com/avatars/chat-icon.png"
}
}
});
Channel types

PubNub's Chat SDK supports different channel types for various use cases:

  • Public channels: For mass participation (like the "Game Chat" in this demo)
  • Group channels: For smaller, focused discussions (ideal for "break-out rooms" in events)
  • Direct channels: For private one-to-one conversations between users

Choose the appropriate channel type based on your application's communication patterns.

You can see the channel details also by accessing BizOps Workspace -> View channel in the left navigation menu on the Live Events demo.

Game Chat channel details

You'll be taken to channel profile details on the Admin Portal in the Channel Management section of BizOps Workspace:

Game Chat channel details - Admin Portal

Read-only vs. full access

The demo uses a special read-only account on the Admin Portal for demonstration purposes. When using your own PubNub keyset, you would have full write access to modify channel details directly through this interface.

Connect to a channel

Once created, users connect to the channel to receive messages:

// Retrieve the channel object and associated metadata
const channel = await chat.getChannel("game.chat");

// Listen for incoming messages
const unsubscribe = channel.connect((message) => {
// Handle new messages as they arrive
console.log(`New message from ${message.userId}: ${message.text}`);
// Update UI with the new message
});

The Live Events demo app doesn't implement message history to focus on the real-time experience. In your own implementation, you could add message history support with code like:

// Retrieve message history when joining
const messageHistory = await channel.getHistory({ limit: 50 });
console.log(`Loaded ${messageHistory.messages.length} previous messages`);

User management

PubNub's Chat SDK enables applications to create, retrieve, update, and authorize users with persistent profiles. While the SDK supports comprehensive user management, the Live Events demo implements a simplified subset of these capabilities.

The Live Events demo showcases basic user profile management with the Chat SDK:

Channel members

Demo simplifications

For simplicity, the demo allows you to log in as one of the pre-created users without authentication. The demo doesn't implement user creation, deletion, or profile editing capabilities that would be typical in a production application.

The demo does demonstrate real-time propagation of certain user data - for example, if a user's score is updated in App Context, that change will immediately reflect in the demo UI without requiring a refresh.

icon

Under the hood


Manage users

The demo uses PubNub's Chat SDK to create and manage these user profiles:

// User profile structure in PubNub
const userProfile = {
// Standard user fields
id: "user-123",
name: "User Name",
profileUrl: "https://example.com/avatar.jpg",

// Optional standard fields - not all may be used in every implementation
email: "user@example.com", // Unused in the Live Events demo
externalId: "EXT12345", // Unused in the Live Events demo

// These are the kinds of custom user data you might wish to store in App Context
custom: {
location: "San Francisco, USA",
jobTitle: "Product Manager",
show all 21 lines

Predefine user profiles

For example purposes only, when the Live Events demo initializes, it uses PubNub's Chat SDK to create a number of predefined user profiles like Emily Johnson, Sarah Wilson, and others.

// The demo initializes PubNub's Chat SDK
const chat = await Chat.init({
publishKey: "pub-key",
subscribeKey: "sub-key",
userId: "temporary-id",
});

// The demo creates predefined users using the Chat SDK
for (const userProfile of predefinedUsers) {
await chat.createUser(userProfile.id, {
name: userProfile.name,
profileUrl: userProfile.avatar,
email: userProfile.email,
custom: {
location: userProfile.location,
show all 23 lines

You can view all these predefined users in the demo admin account.

Integration with your identity provider

In a production application, you would typically integrate PubNub's user management with your existing identity provider. During your application's user registration workflow:

  1. When a new user creates an account in your system, they are assigned an ID by your identity provider
  2. Your application would also call createUser() to create the corresponding PubNub user
  3. You have two options for ID management:
  • Reuse the ID from the external identity provider as your PubNub ID
  • Create separate unique IDs for PubNub and link them using the externalId field, which would be populated with the ID from your external identity provider

This integration ensures consistent user identity across your entire system. The demo simplifies this by creating users without passwords or connections to separate identity providers.

You can see the currently logged-in user details also by accessing BizOps Workspace -> View user in the left navigation menu on the Live Events demo.

User details

You'll be taken to user profile details on the Admin Portal in the User Management section of BizOps Workspace:

User details - Admin Portal

Read-only vs. full access

The demo uses a special read-only account on the Admin Portal for demonstration purposes. When using your own PubNub keyset, you would have full write access to modify user details directly through this interface.

A key advantage of PubNub's User Management is that any changes made to user profiles in the Admin Portal take effect immediately in the running application. For example, changing a user's score will instantly update in the live application without requiring any refresh or restart. This real-time synchronization demonstrates how PubNub differs from traditional user databases in how it broadcasts profile changes to all connected clients.

Applications can easily receive and respond to these updates by using the Chat SDK's getUserUpdates() method:

// Subscribe to updates for all users
const unsubscribe = chat.getUserUpdates((updatedUser) => {
console.log(`User ${updatedUser.id} was updated`);
// Update UI with the new user data
});

With this approach, when a user's score changes (whether modified through the Admin Portal or programmatically), it will instantly update in all connected applications without requiring any refresh or restart. This demonstrates how PubNub differs from traditional user databases, where changes typically require polling or manual refresh operations.

See it in action

While the Live Events demo has limited editing capabilities, for a comprehensive run through all of our user management features, contact sales for a demo.

Authorize users

When a user logs in by selecting a profile, the demo uses the Chat SDK with PubNub Access Manager to authorize that user with the appropriate permissions:

// User selects a profile
async function login(userId) {
// Get authorization token from Access Manager server
const { accessManagerToken } = await getAuthKey(userId);

// Initialize Chat SDK as the selected user with the secure token
const chat = await Chat.init({
publishKey: process.env.NEXT_PUBLIC_PUBNUB_PUBLISH_KEY,
subscribeKey: process.env.NEXT_PUBLIC_PUBNUB_SUBSCRIBE_KEY,
userId: userId,
authKey: accessManagerToken // Access Manager token defines user permissions
});

// Now we can interact as this user with appropriate permissions
setChat(chat);
show all 16 lines

The getAuthKey() function makes a request to a secure token server that generates a PubNub Access Manager token specific to the user.

This server-side approach keeps permission logic secure:

// Request a user-specific token from the Access Manager server
export async function getAuthKey(userId) {
const TOKEN_SERVER = "YOUR_TOKEN_SERVER";

const response = await fetch(`${TOKEN_SERVER}/grant`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ UUID: userId }),
});

const data = await response.json();
if (data.statusCode === 200) {
return { accessManagerToken: data.body.token };
}
return { accessManagerToken: undefined };
show all 16 lines

The returned token contains the precise permissions needed for the user to participate in the Game Chat and other channels. While the exact permission structure is defined server-side, the token typically enables:

  • Reading and writing messages in the Game Chat public channel.
  • Adding reactions to messages.
  • Updating the user's own profile in App Context.
  • Viewing other users' profiles and presence information.
icon

Under the hood


This token-based authorization creates a secure, permission-based system without requiring the demo to implement its own user authorization infrastructure.

Message history

PubNub's Message Persistence stores chat messages on servers, allowing users to access conversation history.

Messages are captured with precise timestamps, complete with content and metadata. When retrieving messages, you can specify both a time period (the timespan of messages you're interested in) and a message count (the maximum number of messages to return in a single call for pagination).

icon

Under the hood

Store messages

The Live Events demo uses PubNub's Message Persistence to store chat messages:

// Using MessageDraftV2 API to send messages with persistence
const handleSendMessage = async () => {
if (messageDraftRef.current && messageInput.trim()) {
try {
// The send() method automatically stores messages in Message Persistence
// by default, allowing them to be retrieved later if needed
await messageDraftRef.current.send();

setMessageInput('');
// Create new message draft for next message
messageDraftRef.current = channel.createMessageDraftV2({
userSuggestionSource: 'channel'
});
} catch (error) {
console.error('Error sending message:', error);
show all 18 lines

Behind the scenes, the MessageDraftV2's send() method uses PubNub's publish functionality with Message Persistence enabled, storing each message with its timestamp, sender information, mentions, and content.

To load message history when a user joins a channel, you would use code like this:

// In setupActiveChannel function
try {
const history = await channel.getHistory();
setMessages(history.messages || []);
} catch (error) {
console.error('Error fetching message history:', error);
setMessages([]);
}

You can also configure message history retrieval with additional parameters:

// Get previous messages when joining a channel with a specific limit
const messageHistory = await channel.getHistory({ limit: 50 });
if (messageHistory?.messages) {
setMessages(messageHistory.messages);
}

You can also configure Message Persistence with additional options like time-to-live (TTL):

// When using sendText() directly, you can specify storage parameters
await channel.sendText("Important announcement!", {
storeInHistory: true, // Explicitly set storage flag
ttl: 24 // Store for 24 hours
});

Retrieve messages

In a production chat application, retrieving message history is essential for users to catch up on conversations they missed.

Here's how you would implement message history retrieval with pagination:

// Get message history when joining a channel
const setupActiveChannel = async () => {
if (!chat || !activeChannelId) return;

try {
const channel = await chat.getChannel(activeChannelId);
setActiveChannel(channel);

// Connect to receive new messages
let unsubscribeMessages = channel.connect((message) => {
setMessages(prevMessages => [...prevMessages, message]);
});

// Fetch message history - retrieving the 50 most recent messages
const messagesPage = await channel.getHistory({ limit: 50 });
show all 48 lines

Under the hood, channel.getHistory() calls PubNub's History API to fetch stored messages with their original timetokens, complete with metadata and sender information. It returns messages newest-first by default, though you should typically display them chronologically for a natural reading experience.

The pagination pattern shown above allows your application to efficiently load only the messages needed, rather than fetching the entire conversation history at once. This is especially important for channels with thousands of messages or long-running conversations.

User presence

PubNub's Presence API automatically tracks which users are currently subscribed to a channel and the total occupancy of a channel.

The Game Chat panel in the Live Events demo displays the current number of online users with an indicator showing "X online" in the channel header.

User presence

The Live Events demo implements presence monitoring using real-time Presence API.

For actual user presence tracking, the demo uses PubNub's core presence features with an optimized configuration:

// Setting up presence monitoring for a channel
const setupActiveChannel = async () => {
// ... other channel setup code

// Access the core PubNub SDK through the Chat SDK
const reactionsChannel = chat.sdk.channel(streamReactionsChannelId);

// Create a subscription with presence events enabled
const reactionsSubscription = reactionsChannel.subscription({
receivePresenceEvents: true // Enable receiving presence events
});

// Set up a handler for presence events
reactionsSubscription.onPresence = (presenceEvent) => {
if (presenceEvent?.occupancy > 0) {
show all 36 lines

Alternatively, the Chat SDK provides a simpler way to monitor presence using the streamPresence() method:

// Using the Chat SDK's presence monitoring
const channel = await chat.getChannel("game.chat");
const stopUpdates = channel.streamPresence((userIds) => {
console.log("Currently present users: ", userIds);
setOnlineUsersCount(userIds.length);
});

// Later, when cleaning up
stopUpdates();

This approach abstracts away the details of working with the core PubNub SDK, making presence implementation more straightforward.

icon

Under the hood

Performance optimization for large audiences

The Live Events demo uses a critical optimization in the PubNub keyset configuration: it's set to process only interval events rather than individual join/leave events. In the Admin Portal configuration, the Announce Max parameter is set to 0, which means:

  • Individual join and leave events are never fired
  • Only periodic interval events are sent (every 10 seconds)
  • These interval events contain the current occupancy count

This approach significantly reduces the number of presence events that need to be processed, making it ideal for applications with many participants like live events. Instead of generating potentially thousands of individual join/leave events that could overwhelm your clients, the system sends compact periodic updates with just the current occupancy numbers.

Behind the scenes, the implementation:

  • Enables presence event handling via the subscription({ receivePresenceEvents: true }) option
  • Sets up an onPresence event handler to process these interval occupancy updates
  • Fetches an immediate snapshot of current channel occupancy using PubNub's hereNow() method
  • Properly manages the subscription lifecycle with subscribe() and unsubscribe() methods

User mentions

User mentions allow chat participants to directly reference other users in their messages by typing "@" followed by a username. This common social media pattern creates more engaging conversations by directing attention to specific participants and notifying mentioned users.

PubNub's Chat SDK provides built-in support for user mentions, enabling this interaction pattern with minimal development effort. When a user is mentioned, the message contains special metadata that can trigger notifications and apply distinct styling to the mentioned username.

The Live Events demo implements this feature to enable viewers to tag each other during discussions about the game, fostering more directed and interactive conversations.

User mentions

The demo uses the JavaScript Chat SDK (@pubnub/chat) and its MessageDraftV2 API, which provides specialized methods for handling rich message content, including mentions.

// Initialize a message draft when the chat component mounts
useEffect(() => {
// MessageDraftV2 maintains the state of message being composed
// with special handling for mentions and other rich content
messageDraftRef.current = channel.createMessageDraftV2({
userSuggestionSource: 'channel' // Suggest users from current channel
});

return () => {
// Cleanup message draft when component unmounts
messageDraftRef.current = null;
}
}, [channel]);

// Handle input changes and detect potential mentions
show all 85 lines
User suggestion source options

The userSuggestionSource parameter determines where user suggestions for mentions are sourced from:

  • channel: Only suggests users who are in the current channel (default and used in the demo)
  • global: Suggests users from the entire application. This requires unchecking Disallow Get All User Metadata under App Context settings in your PubNub Admin Portal keyset configuration.

Behind the scenes, the MessageDraftV2 API manages the entire mention lifecycle by tracking mention positions within message text, associating user IDs with display names, formatting the mentions with special metadata during transmission, and delivering them as structured content that receiving clients can parse and display with distinctive styling.

icon

Under the hood

Message reactions

Message reactions allow users to respond to messages with emoji, providing a lightweight way to acknowledge or express sentiment without sending a full reply. The Live Events demo offers five specific reactions (👍, ❤️, 😂, 😮, 👏) that viewers can use to respond to user-generated chat messages.

Emoji flexibility

While the demo uses these five specific emoji reactions as a design choice, the Chat SDK can handle any emoji that can be rendered by your client. You're not limited to these predefined reactions when implementing your own application.

PubNub's Chat SDK includes built-in support for message reactions, enabling users to add, remove, and view emoji reactions on any message. These reactions are synchronized across all clients in real-time.

Message reactions

The Live Events demo implements message reactions using the JavaScript Chat SDK (@pubnub/chat) and, for simplicity, enables them only for user-generated messages:

// Define the available reactions
export const reactions = ["👍", "❤️", "😂", "😮", "👏"];

// Toggle (add or remove) a reaction on a message
const handleReaction = async (event, emoji) => {
event.stopPropagation();

try {
// Toggle the reaction on the message
// If the user hasn't added this reaction yet, it will be added
// If the user has already added this reaction, it will be removed
await message.toggleReaction(emoji);
} catch (error) {
console.error('Unable to toggle reaction:', error);
}
show all 40 lines

Behind the scenes, the Chat SDK manages reactions as message metadata, storing which users have reacted with which emojis.

The toggleReaction() method communicates with PubNub's Message Actions API to add or remove the reaction, which is then synchronized to all clients.

icon

Under the hood

Display reactions

A key aspect of the Live Events demo is how it displays and updates reactions in real-time. The message.reactions object contains the complete state of all reactions on a message:

// The message.reactions structure looks like this:
/*
{
"👍": [
{ "uuid": "user-123", "actionTimetoken": "16983289196082900" },
{ "uuid": "user-456", "actionTimetoken": "16983290238341020" }
],
"❤️": [
{ "uuid": "user-789", "actionTimetoken": "16983290238341020" }
]
}
*/

// In the demo, we display the count of users who reacted with each emoji
const renderMessageReactions = (message) => {
show all 36 lines

To receive real-time updates when reactions change, the demo uses Message.streamUpdatesOn() to register a callback that's invoked whenever any message's reactions are updated:

// Register for updates to any message in the current list
useEffect(() => {
if (!messages.length) return;

// This will be called whenever any message's reactions change
const unsubscribe = Message.streamUpdatesOn(messages, (updatedMessages) => {
// Update the messages in our state
setMessages(prevMessages => {
// Replace any messages that were updated
return prevMessages.map(msg => {
const updated = updatedMessages.find(u => u.timetoken === msg.timetoken);
return updated || msg;
});
});
});
show all 18 lines

When a user adds a reaction, PubNub delivers this update to all connected clients, allowing them to see the reaction count increase in real-time. Similarly, when a user removes their reaction, all clients see the count decrease.

This provides a consistent, shared experience that makes the chat more interactive during live events, focusing user interaction on meaningful user-generated content rather than automated messages.

Last updated on