Messaging Platform SDK

An SDK to facilitate agent and consumer interactions on LivePerson's Messaging Platform from Node.js and web applications. Can be used to create bots, system tests, or custom UIs.

For more information about specific classes, functions, or enums see the API Reference.

View the changelog here.

Messaging Platform SDK has replaced Messaging Agent SDK (aka Node Agent SDK) as the recommended method for interacting with Messaging Platform from a node.js application. See the Feature Comparison for more information. For information about converting a Node Agent SDK based project to use Messaging Platform SDK, see our Conversion Guide.

Table of Contents

How To Install

To install as a dependency, run the following from a terminal window:

npm install lp-messaging-sdk

Quick Start

A simple consumer conversation

const lpm = require("lp-messaging-sdk");

const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId: '12345678',
    userType: lpm.UserType.CONSUMER
});

// log any internal errors (auth errors, etc)
connection.on('error', err => {
    console.error(err);
});

// connect & open conversation
await connection.open();

// optionally set the consumer's name information
await connection.setUserProfile({firstName: 'firstName', lastName: 'lastName', nickName: 'nickName'});

// create conversation
const conversation = await connection.createConversation();

// setup a message notification listener
conversation.on('message', message => {
    console.log(JSON.stringify(message.body));
});

// send a message
await conversation.sendMessage('test');

// close the main dialog
await conversation.close();

// close the connection
await connection.close();

A simple agent conversation listener bot

const lpm = require("lp-messaging-sdk");

// define the auth data
const accountId = '12345678';
const authData = {
    username: 'bot1',
    appKey: '1a804df636f347bgcb4974a1ea3e2a91',
    secret: 'e15d540710838b40',
    accessToken: 'ccf8gf5bb346f3a95245e9b4798695f2',
    accessTokenSecret: '876c7425f81c5160'
};

// create the connection object
const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId,
    userType: lpm.UserType.BRAND,
    authData
});

// log any internal errors (auth errors, etc)
connection.on('error', err => {
    console.error(err);
});

// setup the conversation event
// this event will fire whenever the bot is informed of a new conversation
connection.on('conversation', async conversation => {

    // join the conversation as "AGENT" role
    await conversation.join(lpm.ParticipantRole.AGENT);
    
    // listen for messages from the consumer
    conversation.on('message', message => {
        
        // ignore all messages not from the consumer
        if (message.participant.role !== lpm.ParticipantRole.CONSUMER) return;
        
        console.log(message.body);
        
        // send a simple response
        conversation.sendMessage('hello');
    });
    
    // listen for the close event
    conversation.on('close', () => {
        console.log('conversation closed');
    });
    
});

// make the connection
await connection.open();

Commonly Used Features

Application Tracking

In order to help us provide the best support, we require that you choose an appId for your application. appId should only contain characters a-z and/or the special characters: -_.. This should be passed in the createConnection function along with the version of your application if available.

const consumerConnection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER
});

Conversations

Create Conversation

When a consumer connection calls createConversation with no arguments, the conversation will be created in the default skill: "-1".

const conversation = await connection.createConversation();
expect(conversation.skill.skillId).toEqual('-1');

To create a conversation in a specific skill, simply pass the skillId as an argument when calling createConversation.

const conversation = await connection.createConversation({skillId: '12345678'});

See Conversation Context and Campaign Info for two other arguments that can be passed to createConversation.


Conversation Functions

Join and Leave

One way to join a conversation is to invoke the join function on that conversation, passing the role that the current user should join the conversation as. Commonly used roles are ASSIGNED_AGENT, AGENT, MANAGER, and CONTROLLER. The other main way to join a conversation as the assigned agent is to accept a ring.

await conversation.join(lpm.ParticipantRole.ASSIGNED_AGENT);

To leave a conversation, use the leave function.

await conversation.leave();
Transfer

To transfer a conversation, use the transfer function. The transfer function will remove the assigned agent from the conversation (if present) and then transfer the conversation to the specified skill or agent. Keep in mind that if you try to transfer without having an open conversation you will get an error.

NOTE: when transferring to a specific agent, specify the whole agent id, i.e. accountId.agentId, instead of just agentId.

// transfer to a specific skill
await conversation.transfer({skillId: '1111111'});

// transfer to a specific agent 
await conversation.transfer({agentId: '2222222.5555555'});
Close

You have the option to close either the main dialog or the entire conversation. Here's how each works:

  • Closing the Main Dialog: When you close the main dialog, the conversation remains open as long as there are other dialogs configured. For instance, post-conversation surveys might run in a separate dialog after the main dialog ends. If no additional dialog is configured, the conversation will automatically close.

  • Closing the Conversation: Closing the entire conversation prevents any further dialogs from appearing, including post-conversation surveys. This is important to consider if you plan to configure post-conversation surveys in the future.

To close only the main dialog while leaving the conversation open, use the following method:

await conversation.getDialog(DialogType.MAIN).close()

To close the entire conversation, use the close function on the conversation object. Be aware that doing so will disable post-conversation surveys:

await conversation.close();

Alternatively, you can close a conversation by passing its conversationId to the closeConversation method of the connection object. This does not require a conversation object, but keep in mind that post-conversation surveys will not run:

const conversationId = '123456789';
await connection.closeConversation(conversationId);
Resume conversation

To resume previously closed conversation you can use the resumeConversation function

const resumedConversation = await conversation.resumeConversation();
Get Latest Consumer Message

To get latest consumer message you can use getLatestConsumerMessage function. Note that, the function does not attempt to retrieve messages over the network. This means the latest consumer message will only be available if was received before.

const latestConsumerMessage = conversation.getLatestConsumerMessage();

Rings

Agent Routing Tasks aka "Rings"

Most bots are configured for the role of MANAGER and receive conversations by virtue of being subscribed to all conversations, and thus they will get their conversations through the connection.on('conversation') event.

Agents and agent-type bots, on the other hand, get notified that they should handle a certain conversation through a process called "agent routing". In this process, the bot must indicate it is open to accepting routing tasks otherwise known as "rings" by setting their agent state to "online" and creating a routingTaskSubscription, which will then emit ring events when an agent has been assigned to handle a conversation.

Note: If an agent (bot or human) accepts a ring, the agent is assigned to the corresponding conversation. When the agent rejects the ring or lets it expire the corresponding conversation is moved back to the queue. This means, that when multiple agents with the same skill are online, rings might not arrive immediately - because other agents are ringed first.

When a ring is received, the agent has the option to either accept or reject the ring. Accepting the ring will add the agent as a participant on the conversation with the role of ASSIGNED_AGENT.

// connect as admin for brand
const connection = lpm.createConnection({appId: 'quick_start', userType: lpm.UserType.BRAND, accountId, authData});
await connection.open();

// agent state must be "ONLINE" in order to receive rings
await connection.setAgentState({agentState: lpm.AgentState.ONLINE});

// subscribe to routing tasks (rings)
const taskRoutingSubscription = await connection.createRoutingTaskSubscription();

// process the rings as they arrive
connection.on('ring', async ring => {
    
    // ignore old rings
    if (ring.ringState !== lpm.RingState.WAITING) return;
    
    // accept the ring
    // full conversation will appear from the connection.on('conversation') event (for now)
    await ring.accept();
    
    // or reject the ring
    // await ring.reject();
});

connection.on('conversation', conversation => {
    console.log('agent has been added to the conversation');
});

Accepting conversation on the ring

As convenience, you can fetch the conversation, which is assigned to the ring. The default timeout is two seconds with 100 ms polling intervals. You can set a different timeout in milliseconds when establishing the brand connection.

// connect as admin for brand
const connection = lpm.createConnection({appId: 'quick_start', userType: lpm.UserType.BRAND, accountId, authData, ringConversationTimeout: 2000});
await connection.open();

// agent state must be "ONLINE" in order to receive rings
await connection.setAgentState({agentState: lpm.AgentState.ONLINE});

// subscribe to routing tasks (rings)
const taskRoutingSubscription = await connection.createRoutingTaskSubscription();

// process the rings as they arrive
connection.on('ring', async ring => {
    
    // ignore old rings
    if (ring.ringState !== lpm.RingState.WAITING) return;
    
    // accept the ring
    await ring.accept();

    // await fetching conversation
    const conversation = await ring.conversation();
    
    // or define callback to avoid pausing code execution
    ring.conversation().then((conversation) => {
       // process conversation and/or execute code that depends on the conversation to have already been fetched
    });

    // or reject the ring
    // await ring.reject();
});


FAQ

Q: When a ring is accepted, can it be accepted by another agent? A: A ring is always specific for one agent. It cannot be accepted twice. When a ring is accepted, the conversation is assigned to this bot or agent.
Q: Can two different rings share the same id? A: Two different rings always have a different id. When a conversation is sent back to the queue, a new ring for a different agent with a different id will be created. The ring id has this pattern: dialogId_brandId_timeStampInMilliSeconds.
Q: How is the skillId of the ring determined? A: A skill id is related to a conversation. It is selected by a rule engine or by the engagement when the conversation is opened by a consumer. The skill id of a ring can change when the conversation is transferred to a different skill.
Q: What happens when an agent rejects a ring? A: Agents assigned to the skill of the conversation are ringed one after another. If all reject the ring, the first agent will be ringed again. For example, given three agents with skill A. When there is a new conversation for skill A and the first agent rejects the ring, then the conversation is put back in the queue. A new ring is created for the second agent. If all three agents reject the ring, the first agent will be ringed again.

Conversation Events

With the Messaging Platform SDK, it is possible to set up hooks for events in a conversation.

Conversation Event: close

To know when a conversation closes, watch the close event. Please note that most conversation actions are not available for closed conversations, such as sending a message or transfer.

conversation.on('close', async () => {
    // TODO: close action
});

Conversation Event: transfer-skill

The transfer-skill event will fire when the conversation is transferred from one skill to another. Usually this also involves the assigned agent being removed.

One potential use case for conversation hooks is to implement holder bots that handles conversations when the contact center is in off hours and there are no available agents:

conversation.on('transfer-skill', async () => {
    
    // if the new skill is in off hours
    if (conversation.skill.isInOffHours()) {
        
        // join the conversation and send a message to inform the consumer
        await conversation.join(lpm.ParticipantRole.AGENT);
        await conversation.sendMessage('we are in off hours, would you like to wait?');
    }
});

Conversation Event: transfer-agent

The transfer-agent event will fire when the conversation is transferred directly from one assigned agent to another.

conversation.on('transfer-agent', async () => {
    
});

Conversation Event: back-to-queue

The back-to-queue event will fire when the assigned agent is removed from the conversation (without adding another) and the skill does not change.

conversation.on('back-to-queue', async () => {
    
});

Conversation Event: consumer-step-up

The consumer-step-up event will fire when the consumer becomes authenticated during a conversation.

conversation.on('consumer-step-up', async () => {
    
});

Conversation Event: participant-added

The participant-added event will fire whenever any participant is added to the conversation, this includes bots and managers.

It is also possible to extend the agent greeter bot to react to new participants on the conversation, a hook for participant-added event can be added:

// listen for new participants on the conversation
conversation.on('participant-added', async addedParticipant => {

    // if a manager joins, send a private message that is hidden from the consumer
    if (addedParticipant.participant.role === lpm.ParticipantRole.MANAGER) {
        await conversation.sendPrivateMessage('a manager has joined the conversation');
    }
});

Conversation Event: participant-removed

The participant-removed event will fire whenever any participant is removed from the conversation.

conversation.on('participant-removed', async (removedParticipant) => {
    
});

For a complete list of events emitted by the conversation object, see Events section of Conversation class


Messages

To send a message to a conversation there are several methods you can use:

// plain text
await conversation.sendMessage('hello');

// rich text
await conversation.sendRichText(richTextObject);

// brand connections can send private messages to a conversation that consumers will not see
await conversation.sendPrivateMessage('this is private');

Markdown in Messages

Messages can include markdown encoding by wrapping the mardown in the #md# tag. One common use is to use this to create a hyperlink via the markdown syntax of [label](url).

await conversation.sendMessage('view our docs #md#[here](https://developers.liveperson.com)#/md#');

Message Events

To receive messages you can watch the conversation's message event. If the user is an assigned agent or a consumer, your application should call the accept and read functions in order to tell the system that the message has been accepted and read (where appropriate).

// listen for messages from the consumer
conversation.on('message', async message => {
    
    // ignore all messages not from the consumer
    if (message.participant.role !== lpm.ParticipantRole.CONSUMER) return;
    
    // indicate that our application has received the message
    await message.accept();
    
    // write the message out to log
    console.log(message.body);
    
    // indicate that the user had read the message
    await message.read();
});

Previous messages

All received messages for a dialog will be stored in the messages array of that dialog. You can iterate through that array to write these messages to the screen or a log.

for (const message of conversation.openDialog.messages) {
    console.log(message.body);
}

Message is sent by current user

To check if a message is self sent (sent from the same user) you can use the sentByCurrentUser() method on every message.

message.sentByCurrentUser();

Loading all previous messages

By default, messages will only have the messages that have been received since the conversation was joined. For consumer connections, all messages are automatically retrieved for open conversations. For brand connections, messages are not automatically retrieved, because this could result in excessive memory usage for accounts with a large number of open conversations. Instead, you can call getAllPublishEvents on a conversation's dialog to ensure that all of its messages have been loaded into the messages array.

connection.on('conversation', async conversation => {
  // Load events for the open or main dialog
  const dialog = openDialogOrMainDialogIfAvailable(conversation)
  if (dialog) {
    await dialog.getAllPublishEvents();
  }
  // [execute conversation code]
});

/**
 * Returns the open dialog or the main dialog if the conversation is closed.
 * 
 * @param conversation of the dialog
 * @returns {(null|dialog)}
 */
function openDialogOrMainDialogIfAvailable(conversation) {
  return conversation.openDialog ?? conversation.getDialog(DialogType.MAIN);
}

However, you can force the SDK to automatically call getAllPublishEvents on all conversations as they are received, before they are emitted via the conversation event, by passing the argument getAllMessages: true when calling createConnection

const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    getAllMessages: true
});

Message Metadata

To attach metadata to a message, pass it in as the second parameter to sendMessage, sendRichText, or sendPrivateMessage. The structure of metadata objects is strictly validated, please contact the Messaging Platform team for more information.

// TODO: ensure the metadata structure conforms to a valid metadata definition based on type
const metadata = {
    type: 'ExternalId',
    id: '123'
};
await conversation.sendMessage('external action 123 was taken', metadata);

Metadata can also be an array of metadata objects, if you need to attach more than one:

const metadata1 = {type: 'ExternalId',id: '1'};
const metadata2 = {type: 'ExternalId',id: '2'};
await conversation.sendMessage('external action 123 was taken', [metadata1, metadata2]);

Message quick replies

To attach quick replies to a message, pass the object as the third parameter to sendMessage or sendRichText. The structure of quick reply objects is strictly validated, please contact the Messaging Platform team for more information.

// TODO: ensure the quickreplies structure conforms to a valid definition
const quickReplies = {
    "type": "quickReplies",
    "itemsPerRow": 3,
    "replies": [
        // TODO: insert quickreplies objects here
    ]
};
await conversation.sendMessage('here are some quick replies', null, quickReplies);

File Sharing

Accounts that have filesharing enabled can upload and download files using the SDK. The article also describes the limits such as file size, formats, supported channels

To upload files, pass an ArrayBuffer to the Conversation.uploadFile method. An optional caption can also be passed in as the second argument.

If the upload succeeds, the SDK will then publish a message on the conversation with the uploaded file and caption (if any), which makes it viewable by the conversation participants. For image files, the SDK will generate a preview thumbnail that gets displayed in supported channels.

Upload a file from Node.js

To upload a file In Node.js environments, access the file with the File System module to generate a buffer.

const fs = require('fs');
const path = require('path');

// read the file into a buffer
const file = fs.readFileSync(path.resolve(__dirname, 'asset/logo.png'));

// upload the buffer
await conversation.uploadFile(file.buffer);

Download a file from Node.js

Whenever the conversation has a downloadable message with a file, the file can be downloaded.

// set up a hook to download files in the conversation
conversation.on('message', async (msg) => {
    
    // ignore regular messages without files
    if (!msg.isDownloadable) {
        return;
    }
    
    // download the file
    const downloadedFile = await msg.downloadFile();
    
    // write the file out to disk
    const buffer = downloadedFile.data;
    const filename = downloadedFile.filename;
    fs.writeFileSync(path.resolve(__dirname, `asset/${filename}`), data);
});

Upload a file from browser

For browser implementations, the SDK supports client-side scripting with JavaScript. To allow a user to upload a file to a conversation, first create an input tag of type file, an event that pulls the data from that file, and uploads it to the conversation.

<div id="file-upload" style="padding: 4px">
    <input type="file" id="selected-file">
    <button id="upload" value="upload">Upload</button>
</div>

<script type="text/javascript">
    const selectedFile = document.querySelector('#selected-file');

    async function upload(buffer) {
        await conversation.uploadFile(buffer);
    }

    document.querySelector('#upload').addEventListener('click', () => {
        console.log('uploading file');

        const file = selectedFile.files[0];
        
        // create a FileReader instance and read the selected file as arrayBuffer
        let fr = new FileReader();
        fr.onload = function () {
            let data = fr.result;
            upload(data).then(() => {
                writeLine('file uploaded');
            });
        };
        fr.readAsArrayBuffer(file);
    });
</script>

Download a file in browser

To download a file, set up a message event as before, but use the download function below in order to cause the browser to prompt the user to save the file to disk.


    conversation.on('message', async (msg) => {
        // ignore regular messages without files
        if (!msg.isDownloadable) {
            return;
        }
        await download(msg);
    });

    async function download(message) {
        // execute the real download
        const {data} = await message.downloadFile();
        
        // convert arrayBuffer to Blob
        const blob = new Blob([data]);
        
        // obtain the filename 
        const path = message.relativePath.split('/');
        const filename = path[path.length - 1];
        
        // create a temporary element that references the downloaded data
        let link = document.createElement('hiddenDownloadElement');
        link.href = window.URL.createObjectURL(blob);
        link.download = filename;
        
        // downloads the data as a file
        link.click();
        
        // remove the temporary element
        window.URL.revokeObjectURL(blob);
        link.remove();
    }

Advanced Topics

Background Process Errors

There are many active processes running under the hood of the SDK, sometimes these will encounter an error scenario. They will do their best to recover, but it is advised that applications watch and log the error event. To do this, simply add an event watcher to the connection and log the error as you would any other error in the application. If you get any unexpected errors that you can't resolve on your own, please reach out to a LivePerson team member for assistance.

connection.on('error', err => {
    console.error(err);
});

Arbitrary websocket requests

Our goal in making this SDK is to support all Message Platform requests through nicely designed interfaces. However, there may be some requests or edge cases for existing requests that the SDK does not yet support. If there is a type of request that you want to execute for which the SDK does not yet officially support, you can send the request directly using the send function.

The send function is async and will wait for the response back before completing. For example, to send a plain text message, instead of using sendMessage you can use the following code:

const request = {
    type: '.ams.ms.PublishEvent',
    body: {
        dialogId: 'MY_DIALOG_ID',
        event: {
            type: 'ContentEvent',
            contentType: 'text/plain',
            message: 'hello world!'
        }
    }
};

const response = await connection.send(request);

Notification Events

There are three different kinds of messages used in communicating with Messaging Platform: Request, Response, and Notification. For the most part, when using the SDK your application does not need to consider these, but we want to provide the information just in case it is useful.

In general, when you use the SDK to issue a command, it sends a Request to the server, the request is processed and the server returns a Response which then triggers the awaited promise to resolve. So, Response messages are only ever received in response to a Request.

The third type of websocket message is a notification, these are the server's method of communicating with clients asynchronously, without a request originating from the client. They are the main reason that websockets are required, otherwise we could do all communications simply using http.

Notifications are automatically processed by the SDK and may result in one of the events mentioned above. However, some applications might wish to examine notifications directly. In order to do this, watch the notification event on a connection.

connection.on('notification', notification => {
    console.log(`notification received of type ${notification.type}\n${JSON.stringify(notification.body, null, 2)}`);
})

Notification Types

There are three types of Notifications that Messaging Platform can send, each of which has different conditions that must be met in order to receive them. Some of them involve the creation of a subscription telling Messaging Platform which types of notifications the application wants to receive.

Message Event Notification

A message event notification is sent whenever a publishEvent request is successfully processed by the Messaging Platform. There are various types of message events: plain or rich message text from a participant, the status of a participant (typing, away, etc.), a file sharing event, etc.

To receive messages events for a conversation, an agent simply needs to be a participant in the conversation. This is accomplished by joining a conversation or accepting a ring.

Consumer connections, on the other hand, must create a subscription for the conversation before they will receive these notifications. The SDK creates and maintains these subscriptions automatically, there is no need to create them manually.

Conversation State Notification

Conversation state notifications contain information about a conversation's state (whether it is open or closed, or which users are participants, etc.)

To receive conversation state notifications, a conversation subscription is required whose query encompasses that conversation. The SDK creates and maintains these subscriptions automatically, there is usually no need to create them manually, unless you want to see closed conversations on a brand connection, which does not apply to most users.

Routing Notification

A routing task event, aka a Ring, indicating that routing has chosen the current user to handle a conversation.

To receive routing task events, a routing task subscription is required. These must be manually created, for more information see Agent Routing Tasks.

Subscriptions

The default subscription

By default, after connecting the SDK will automatically create a single subscription per connection. This subscription is available on the connection object, if you want to log all notifications that subscription receives you can do so with this code:

connection.defaultSubscription.on('notification', notification => {
    console.log(JSON.stringify(notification));
});

Each type of connection has a different default query that is used to create its default subscription:

  • Brand connections use this query: {stage:["OPEN"]}
  • Consumer connections use this query: {stage:["OPEN", "CLOSE"]}

You can also provide a different query that will be used to create the default subscription, for example if you want your bot to only monitor conversations with an open main dialog, you would create your connection like this:

const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    defaultSubscriptionQuery: {state:["OPEN"], dialogTypes:["MAIN"]}
});

Other possible query properties.

If you don't want the SDK to create the default subscription, you can disable it by passing createDefaultSubscription as false when creating the connection:

const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    createDefaultSubscription: false
});

Note that skillId is not supported in the subscription query.

Creating manual subscriptions

In the event that you want to create a conversation subscription manually, use the "createConversationSubscription" function. Please note that conversation objects are shared between subscriptions, in the sense that the SDK will use any notifications from all active subscriptions to update conversation objects.

const query = {stage:["OPEN"], agentId:['12345.123456']};
const waitForReady = false;
const sub = await connection.createConversationSubscription({query, waitForReady});

waitForReady can be used to wait for conversations to be loaded once the connection is opened. False by default.

Notice: If agentId is not provided - the bots will try to subscribe to all open conversation for the given account.


Query properties

Following properties can be used to filter for conversations in your query.

Property Type and example Description
query.accountId string: "3949245" The account id.
query.agentId Array<string>: ["3949245.agentId1", "3949245.agentId2"] Filters for events of conversations for these agent ids. The id is preceded by the account id.
query.consumerId string: consumerId123 Filters for events of conversations for this consumer.
query.conversationId string: 59999610-d812-4j76-907f-9905as579e5a Filters for events for a specific conversation.
query.state Array<string>: ["OPEN", "CLOSE"] Filters for conversations with a MAIN dialog in given state. Possible values: OPEN, CLOSE, LOCKED, CLOSING
query.stage Array<string>: ["OPEN", "CLOSE"] Filters for conversations being in given stage. Possible values: OPEN, CLOSE, LOCKED, CLOSING
query.dialogType Array<string>: ["MAIN"] Filters conversation events related to given dialog type. Possible values: MAIN, AGENTS, OTHER, POST_SURVEY, PRE_CONVERSATION_OPT_IN
query.lastUpdateBefore number: 1715613775114 Filters for conversations which were updated before given timestamp.
query.lastUpdateAfter number: 1715613775114 Filters for conversations which were updated after given timestamp.

query is a nested object within the connection object. For example:

{
    "waitForReady": true,
    "query": {
        "dialogType": ["MAIN"], 
        "agentId": ["1245.1122"]
    }
}

Connection Maintenance

Communication with Messaging Platform happens primarily over a websocket, the SDK takes responsibility for maintaining this connection and in the event of a connection loss it will attempt to reconnect. The SDK will do this by default, no additional configuration or intervention is required.

While disconnected, any requests will be queued up and will execute when the connection is re-established. The SDK will also attempt to recreate any subscriptions, including the default subscription.

If the auth token has become invalid during the time in which the connection was down, the SDK will attempt to generate a new one based on the Auth Token Process.

Sometimes there could be issues with conversations being lost or stuck after reconnection. If you encounter this, set unsubscribeFromOutdatedConversationsOnReconnect flag to false when you create the connection.

const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authData,
    unsubscribeFromOutdatedConversationsOnReconnect: false
});

Brand Authentication

Different authentication mechanisms

The SDK supports several authenticating mechanisms depending on the credentials provided using authData. For oAuth1, authData is expected to have the following format:

const authData = {
    username: 'botName123',
    appKey: 'appKey',
    secret: 'secret',
    accessToken: 'accessToken',
    accessTokenSecret: 'accessTokenSecret'
}

If you have oAuth2 credentials, you can either use oAuth2 over oAuth1 or just oAuth2. For oAuth2 over oAuth1, the authData looks as follows:

const authData = {
  "username": "username",
  "appKey": "6YxG183r123456",
  "secret": "zQ1wIzypH123456",
  "accessToken": "hint",
  "accessTokenSecret": "hint"
}

The credentials themselves are provided through the authData property in the same way as oAuth1 credentials. The difference being that the values for accessToken and accessTokenSecret MUST be "hint" since they don't exist for oAuth2. For just oAuth2, the authData looks as follows:

const authData = {
  "oauth2": {
    "username": "username",
    "client_id": "6YxG183r123456",
    "client_secret": "zQ1wIzypH123456"
  }
}

If you provide oAuth1 and oAuth2 credentials, the oAuth2 credentials takes precedence. If you use oAuth2, authentication with existing bearer token is not supported and will result in an error emit. If you want to use oAuth2 with an existing bearer token please use oAuth2 over oAuth1.

Brand Authentication Token Process

For brand connections, the SDK will use the provided authData to create a bearer token by making a request to an internal service called agentVep. This token is used to authenticate with the Messaging Platform when making a websocket connection or a rest api request. To guarantee a valid token for every request, there are different mechanisms in place for oAuth1 and oAuth2.

For oAuth1, a refresh request must be made once in every ten minutes back to agentVep so that the token stays valid. The SDK requests a refresh every four minutes by default. For oAuth2, a token expires after 60 minutes and cannot be refreshed. The SDK requests a new oAuth2 token every 30 minutes and stores it internally.

If a token becomes invalid for any reason, the SDK will automatically attempt to create a new one. In general, any websockets established will not lose connection if their token becomes invalid, so there is no risk of service interruption, but the new token will be required before any new http requests can be made.

If an application creates another token for the same user, the first token will become invalid. So it is therefore important that applications do not create two connections with the same authData, this will cause them to continually generate new tokens and put a strain on LivePerson's services. To share the same authentication information across multiple connections, please refer to Brand Authentication using TokenMaintainer.

If an application needs to use a connection's token to make http requests to other LivePerson services that are not supported directly by the SDK, you can access the bearer token with the following method after the connect() finishes: await connection.getToken()


Brand Authentication with existing bearer token

Notice: This mechanism only works with oAuth1 or oAuth2 over oAuth1 authentication. If you have oAuth2 credentials or session information from an oAuth2 connection, this authentication mechanism is not supported.


If you are already authenticated with agentVep and have a token at hand, you can use it with the SDK to establish an authenticated brand connection. Add your bearer token to authAgentSessionData when setting up a connection. If you provide both the authAgentSessionData and authData an error is thrown. The format looks as follows:

const authAgentSessionData = {
    token: 'your-bearer-token',
    userPid: 'uuid-of-the-user',
    csrf: 'csrf-key',
    sessionId: 'session-id'
}

Once this is set, the SDK will utilize the provided token for any subsequent brand requests. Token refresh will be managed automatically by the SDK. Here's an example of how to set up the connection when using authAgentSessionData:

const connection = lpm.createConnection({
    appId: 'quick_start',
    accountId,
    userType: lpm.UserType.BRAND,
    authAgentSessionData,
});

This ensures proper usage of the authAgentSessionData parameter and avoid errors when establishing connections.


Brand Authentication using TokenMaintainer

To share the same authentication information across multiple connections, pass the same TokenMaintainer instance to each connection. The TokenMaintainer handles token refreshes.

const accountId = '1234';   
const authData = { // This can also be oAuth2 credentials
    username: "username",
    appKey: "app-key",
    secret: "secret",
    accessToken: "accessToken",
    accessTokenSecret: "accessTokenSecret",
};

const tokenMaintainer = new TokenMaintainer({accountId, authData});

const connection = lpm.createConnection({
    appId: 'sharedAuthentication',
    application
    accountId,
    userType: lpm.UserType.BRAND,
    tokenMaintainer
});

const connection2 = lpm.createConnection({
    appId: 'sharedAuthentication2',
    accountId,
    userType: lpm.UserType.BRAND,
    tokenMaintainer
});

const connection3 = lpm.createConnection({
    appId: 'sharedAuthentication3',
    accountId,
    userType: lpm.UserType.BRAND,
    tokenMaintainer
});

await connection.open();
await connection2.open();
await connection3.open();

The TokenMaintainer provides several events you can listen to:

  • The error event is triggered whenever an error occurs during refresh.
  • The token-invalid event is triggered when the token becomes invalid.
  • The refresh-token event is triggered when the token is refreshed.
  • The token-regenerated event is triggered when the token is successfully regenerated.

To monitor the requests and responses between the server and the SDK, listen to the getAgentToken#request and getAgentToken#response events. These events are emitted on all connections sharing the same token maintainer.

tokenMaintainer
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

connection
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

connection2
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

connection3
    .on('error', err => {...})
    .on('token-invalid', source => {...})
    .on('refresh-token', () => {...})
    .on('token-regenerated', info => {...})
    .on('getAgentToken#request', info => {...})
    .on('getAgentToken#response', info => {...});

Continuing an anonymous user session between two connections

It is common for users of a web application to refresh their browser, or to close their browser and return to the site at a later time. In these situations, the expectation is that the user resumes the same conversation. However, from an application stand point, there is no way to preserve objects between page refreshes, much less between separate browser processes.

Instead, the solution is to save the JWT generated by the initial connection and give it to the subsequent connections. They can then use this JWT to connect, rather than generate their own new JWTs. This will cause the Messaging Platform to recognize them as the same consumer, and give them access to the existing conversations for that consumer.

  1. Once the initial connection is open, get the jwt by calling await connection.getToken()
  2. Store that token in local storage or a cookie
  3. Pass the token back in to createConnection as token

Any existing conversations will automatically be loaded up into the SDK and emitted as a conversation event. They will also be available through connection._conversations which is a Map. To ensure that that conversation is loaded by the time await connection.open() is resolved, you can pass waitForReady: true to createConnection. This will cause open to only resolve once all conversations have been retrieved.

// open the initial connection
const connection1 = Connection.createConnection({
    userType: UserType.CONSUMER,
    appId: 'quick_start',
    accountId: '123456789'
});
await connection1.open();

// get and store the token
const token = await connection1.getToken();

// create a conv so we can resume it in the 2nd connection
const conversation1 = await connection.createConversation();
await conversation1.sendMessage("hello");

// close connection
await connection1.close();

// open a second connection using the same token
const connection2 = Connection.createConnection({
    userType: UserType.CONSUMER,
    appId: 'quick_start',
    accountId: '123456789',
    token,
    waitForReady: true
});
await connection2.open();

// retrieve the conversation and close it
const conversation2 = Array.from(connection2._conversations.values())[0];
await conversation2.close();

// close connection
await connection2.close();

Client Properties

ClientProperties is an object that contains information about the client that a consumer uses to connect to Messaging Platform. This includes not only device and browser information, but also information about the specific messaging features supported by the particular UI client they are connected through.

Example:

const clientProperties = lpm.createClientProperties({
    deviceFamily: lpm.DeviceFamily.DESKTOP,
    deviceManufacturer: 'Apple',
    deviceModel: 'MacBook Pro',
    os: lpm.OS.OSX,
    osName: 'macOS',
    osVersion: '11.6.8',
    ipAddress: '127.0.0.1',
    browser: 'CHROME',
    browserVersion: '47.0',
    timeZone: 'America/Los_Angeles',
    features: [lpm.Features.AUTO_MESSAGES, lpm.Features.PHOTO_SHARING]
});

const consumerConnection = lpm.createConnection({
    appId: 'quick_start',
    accountId: '123456',
    userType: UserType.CONSUMER,
    clientProperties
});

await consumerConnection.open();

Conversation Context and Campaign Info

To create a conversation with a given context and or campaign, use the following syntax. Keep in mind that client properties should be sent during connection creation.

const context = {
    type: lpm.ConversationContextType.SHARK,
    lang: 'en-US',
    visitorId: '',
    sessionId: '',
    interactionContextId: '2'
};

const campaignInfo = {
    campaignId: '111',
    engagementId: '222'
};

const conversation = await connection.createConversation({context, campaignInfo});

Consumer Auth Flow

By default, consumer connections will be made in "anonymous" mode. Some customers have set up Consumer Authentication for their account. To establish an authenticated consumer connection, your application must provide the useAuthenticatedConnection parameter along with a JWT (JSON Web Token). This combination of useAuthenticatedConnection and JWT prompts the service to retrieve all connectors configured for the brand. Based on the issuer, it matches the connector ID to generate a token, facilitating authenticated consumer access. Before establishing a connection, the brand must undertake an additional step to generate the token, which will be received via a callback. This token should then be added as a parameter to the createConnection function. It's worth noting that the JWT generated by the brand's configured authentication service has an expiration time. Hence, the brand should supply a new token once the current one expires, if necessary.

Migration from the old structure to the new one includes deleting usage of those three methods: createJwtClaimsSet,createCustomerInfoSde, createPersonalInfoSde and start using the corresponding example described here.

const connection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER,
    useAuthenticatedConnection: true,
    jwt: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'
});

Another type of consumer authentication is unauth connection. By providing useUnauthenticatedConnection to the createConnection function the system will know to use unauth connector if such is configured for the brand

const connection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER,
    useUnauthenticatedConnection: true,
});

Getting a User Profile

In order to retrieve authenticated consumer data or an agent's profile, call getUserProfile, which is available on any conversation participant.

const consumerProfile = await conversation.consumer.getUserProfile();
const agentProfile = await conversation.assignedAgent.getUserProfile();

Alternatively, you can access all of the participants of a dialog at conversation.openDialog.participants. Note that consumers can only call getUserProfile on their own participant object; they cannot access agent or bot profiles.


Set User Profile

To set a user profile outside of using the consumer auth flow, you can use setUserProfile. Most of these fields are optional, and it is very common to call this function with just firstName, lastName, and nickName alone.

const data = {
    "firstName": "1",
    "lastName": "2",
    "nickName": "3",
    "userId": "",
    "avatarUrl": "",
    "role": "",
    "backgndImgUri": "",
    "description": "",
    "privateData": {
        "mobileNum": "",
        "mail": "",
        "pushNotificationData": {
            "serviceName": "",
            "certName": "",
            "token": ""
        }
    },
    "claims": {
        "lp_sdes": [
            customerInfoSde,
            personalInfoSde
        ]
    }
};

await connection.setUserProfile(data);

Consumer Step Up

Consumer stepup is a process by which an unauthenticated consumer connection is "stepped up" to be an authenticated connection. First, create an unauthenticated consumer connection by providing the configuration useUnauthenticatedConnection to determine that we will use un auth connector and then opening the connection:

const connection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.6.2',
    accountId: '123456',
    userType: UserType.CONSUMER,
    useUnauthenticatedConnection: true
});
await connection.open();

The consumer can create a conversation as usual. When the consumer logs in and you are ready to upgrade the connection to Messaging Platform, call the stepUp function, passing the jwt. This will get an authenticated JWT, then disconnect and reconnect using the new JWT. It will then issue a "takeover" command for any existing open conversation, so that the authenticated consumer takes ownership of the conversation, thereby completing the stepUp.

await connection.stepUp(jwt);

Message statistics


const userData = await connection.getMessageStatisticsForUser();
const brandData = await connection.getMessageStatisticsForBrand();

That is what getMessageStatisticsForUser/getMessageStatisticsForBrand data returns when you call it.

{
    active: 0,
    pending: 0,
    unassigned: 0,
    overdue: 0,
    soonToOverdue: 0
}

Using proxies

You can configure the SDK to redirect all requests to an HTTP proxy. Once you imported the messaging SDK, use the configureProxy method to pass your proxy configuration. To remove the proxy, call removeProxy. When you configure the proxy before establishing the connection, all requests are proxied. However, you can configure the proxy at any time you want. The configuration structure looks as follows:

{
    host: string,
    port: number,
    protocol: string | undefined,
    path: string | undefined,
    auth: {
      username: string | number,
      password: string | number,
    } | undefined
}

The host can be a name or an address. The port refers to the proxy port. The protocol can either be https or http. It defaults to http. The optional path is added to the URL. The optional auth object carries the username and password for HTTP basic authentication. Path and host should not end with a /. Here is a usage example:


const lpm = require("lp-messaging-sdk");

const proxy = {
    host: 'yourHost.com',
    port: 8080,
    protocol: 'http',
    path: 'user/1',
    auth: {
      username: 'username',
      password: 'password',
    }
};

lpm.configureProxy(proxy);

const connection = lpm.createConnection({
    appId: 'quick_start', // TODO: please change to something unique to your application
    accountId: '12345678',
    userType: lpm.UserType.BRAND,
});

If you want to remove an existing proxy you can do that by calling removeProxy;

const lpm = require("lp-messaging-sdk");

lpm.removeProxy();


Response Timeout

In order to provide you a way of manual configuration of what will be the response timeout instead of using the default one, we are providing responseTimeout option for your application. responseTimeout should only contain digits 0-9. The value should be in milliseconds. responseTimeout should be provided to the createConnection function.

const consumerConnection = lpm.createConnection({
    appId: 'quick_start',
    appVersion: '1.12.2',
    accountId: '123456',
    userType: UserType.BRAND,
    responseTimeout: 10000
});

Features Not yet supported

  • Agent State Subscriptions

  • Conversation/Dialog level metadata (Message Metadata is available)

  • SendAPI