This document serves as a comprehensive resource for developers and engineers seeking to implement efficient and reliable messaging solutions within their web applications. Whether you're building a customer support platform, a real-time chat application, or integrating messaging capabilities into your existing services, this guide offers valuable insights and recommendations to streamline your development process and enhance the user experience.
Web messaging has become an indispensable tool for businesses and organizations looking to engage with their customers in a more personalized and interactive manner. From instant customer support to seamless collaboration, the ability to communicate in real-time has revolutionized the way we interact online. However, building and maintaining a robust messaging infrastructure comes with its own set of challenges, ranging from scalability and security to user experience and interoperability.
In this guide, we will explore best practices for designing, implementing, and maintaining web messaging APIs and SDKs.
1. Initiation
The initiation process of the Messaging Window API involves the consumer initializing the messaging application on their device. This process encompasses several key steps:
- Authentication: The consumer utilizes the Authentication API to pass an external token, which is then exchanged for an API token in the form of a JSON Web Token (JWT). This token serves as the key for subsequent interactions with the messaging service.
- WebSocket Connection: Upon successful authentication, the client establishes a WebSocket connection to the messaging server. This connection facilitates real-time communication and enables efficient data exchange between the client and the server.
- Subscription to Conversations Metadata: After establishing the WebSocket connection, the client subscribes to the conversations metadata using the
cqm.SubscribeExConversations
API. This subscription allows the client to receive notifications about various changes related to conversations, such as new conversation creation, participant updates, and conversation closure.
Notification Example:
javascript:
{
"kind": "notification",
"body": {
"subscriptionId": "c879a0e8-73fd-405c-9363-8ad6e0218fa9",
"changes": [
{
"type": "UPSERT",
"result": {
"convId": "6b2622de-26b7-4e05-a7f2-5c2d50b3502f",
"conversationDetails": {
"participantsPId": {
"CONSUMER": ["72fca421-d1a2-459c-a420-6c5b1f3e75b0"]
},
"state": "OPEN",
"startTs": 1477840809953,
"ttr": {
"ttrType": "PRIORITIZED",
"value": 600
}
},
"lastUpdateTime": 1477840967209
}
}
]
},
"type": "cqm.ExConversationChangeNotification"
}
2. Start a New Conversation
To initiate a new conversation using the Messaging Window API, the client follows a set of defined procedures:
- Request Conversation Creation: The client sends a request to create a new conversation using the
cm.ConsumerRequestConversation
API. This request includes relevant details such as participant information and any initial messages or context. - Receive Response: Upon receiving the request, the server processes it and responds with a
cm.RequestConversationResponse
. This response contains essential information, including the unique conversation ID assigned to the newly created conversation. - Notification: Subsequently, the client receives a notification regarding the new conversation via the
cqm.ExConversationChangeNotification
API. This notification serves as confirmation of the successful creation of the conversation and provides additional details such as participant information and conversation state updates.
3. Continue an Existing Conversation
Continuing an existing conversation involves subscribing to conversation metadata and messaging events, handling various types of events, and maintaining the flow of communication between participants. Here's a breakdown of the process:
- Subscription to Conversation Metadata: Upon initiation, the client subscribes to conversation metadata, specifically targeting conversations with an "OPEN" state. This subscription enables the client to receive notifications about existing conversations and their details, such as conversation ID and participant information.
- Subscription to Conversation Messaging Events: Additionally, the client subscribes to conversation messaging events using the
ms.SubscribeMessagingEvents
API. This subscription allows the client to receive notifications about various types of events within the conversation, including text messages, read receipts, and accept events. - Handling Text Messages: Text messages received from the server should be added to the conversation presentation, ordered by their sequence. Additionally, read and accept events should include a graphical indication next to the corresponding text message they reference.
- Publishing Events: The client is responsible for publishing accept events when new text messages are received, indicating that the message has been successfully delivered to the consumer device. Similarly, when a message is presented to the consumer, the client should publish a read receipt.
- Publishing Text Messages: When the consumer wants to send their own text message, the client publishes the message using the appropriate API.
- Publishing Chat State Events: Chat state events, such as typing indicators, should be published when the consumer starts typing, watching the conversation, or stops watching the conversation.
Example:
javascript:
{
"kind": "notification",
"body": {
"changes": [
{
"sequence": 0,
"originatorClientProperties": {
// Client properties
},
"originatorId": "fa98b947-194d-4dde-aba6-ebf3f38fe3e9",
"serverTimestamp": 1518016462919,
"event": {
"type": "ContentEvent",
"message": "hi",
"contentType": "text/plain"
},
// Other event details
},
// Other events
]
},
"type": "ms.MessagingEventNotification"
}
4. Finish a Conversation
Concluding a conversation involves closing the conversation and optionally collecting feedback from the consumer. Here's how this process unfolds:
- Closing the Conversation: The conversation can be closed either by the agent or the consumer. If the consumer initiates the closure, the client sends a request to update the conversation metadata, setting the state to "CLOSED" using the appropriate API (e.g.,
cm.UpdateConversation
). - Notification of Conversation Closure: LP sends a notification confirming the closure of the conversation.
- Collecting Feedback (Optional): Upon closure, the client may present a Customer Satisfaction (CSAT) survey to the consumer to gather feedback about their experience. The client then sends an update conversation metadata request with the CSAT information using the appropriate API (e.g.,
cm.UpdateConversation
).
5. Message Level Notifications and Publish Events
Message level notifications and publish events are crucial for maintaining real-time communication and tracking the status of messages within a conversation. This chapter outlines various message level notifications and publish events supported by the Messaging Window API.
Message Level Notifications:
AcceptStatusEvent:
javascript:
{
"kind": "notification",
"type": "ms.MessagingEventNotification",
"body": {
"changes": [
{
"sequence": 0,
"originatorId": "",
"serverTimestamp": 0,
"event": {
"type": "AcceptStatusEvent",
"status": "ACCEPT",
"sequenceList": []
},
"dialogId": ""
}
]
}
}
javascript:
{
"kind": "notification",
"type": "ms.MessagingEventNotification",
"body": {
"changes": [
{
"sequence": 0,
"originatorId": "",
"serverTimestamp": 0,
"event": {
"type": "AcceptStatusEvent",
"status": "READ",
"sequenceList": []
},
"dialogId": ""
}
]
}
}
javascript:
{
"kind": "notification",
"type": "ms.MessagingEventNotification",
"body": {
"changes": [
{
"sequence": 0,
"originatorId": "",
"serverTimestamp": 0,
"event": {
"type": "AcceptStatusEvent",
"status": "ACCESS",
"sequenceList": []
},
"dialogId": ""
}
]
}
}
Conversation Level Notifications:
ExConversationChangeNotification:
javascript:
{
"kind": "notification",
"type": "cqm.ExConversationChangeNotification",
"body": {
"subscriptionId": "",
"changes": [
{
"type": "UPSERT",
"result": {
"convId": "",
"conversationDetails": {
"participants": [],
"state": "OPEN",
"startTs": 0
},
"lastUpdateTime": 0
}
}
]
}
}
javascript:
{
"kind": "notification",
"type": "cqm.ExConversationChangeNotification",
"body": {
"subscriptionId": "",
"changes": [
{
"type": "UPSERT",
"result": {
"convId": "",
"conversationDetails": {
"participants": [],
"state": "CLOSE",
"startTs": 0
},
"lastUpdateTime": 0
}
}
]
}
}
Publish Events:
ContentEvent:
javascript:
{
"id": "A3FA",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "ContentEvent",
"contentType": "text/plain",
"message": ""
}
}
}
AcceptStatusEvent:
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "AcceptStatusEvent",
"status": "ACCEPT",
"sequenceList": []
}
}
}
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "AcceptStatusEvent",
"status": "READ",
"sequenceList": []
}
}
}
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "AcceptStatusEvent",
"status": "ACCESS",
"sequenceList": []
}
}
}
ChatStateEvent:
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "ChatStateEvent",
"chatState": "COMPOSING"
}
}
}
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "ChatStateEvent",
"chatState": "ACTIVE"
}
}
}
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "ChatStateEvent",
"chatState": "INACTIVE"
}
}
}
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "ChatStateEvent",
"chatState": "GONE"
}
}
}
javascript:
{
"id": "0",
"type": "ms.PublishEvent",
"body": {
"dialogId": "",
"event": {
"type": "ChatStateEvent",
"chatState": "PAUSE"
}
}
}
UpdateConversationField:
javascript:
{
"id": "0",
"kind": "req",
"type": "cm.UpdateConversationField",
"body": {
"conversationId": "id",
"conversationField": [
{
"field": "ConversationStateField",
"conversationState": "CLOSE"
}
]
}
}
javascript:
{
"kind": "req",
"id": "AHDJE",
"type": "cm.UpdateConversationField",
"body": {
"conversationId": "",
"conversationField": {
"field": "CSATRate",
"csatRate": 5,
"csatResolutionConfirmation": true,
"status": "FILLED"
}
}
}
``
6. Create an API-based engagement window in LE2.0
Create API based window in LE2.0:
Configure settings:
Link engagement to the window:
The method is activated under two scenarios:
- Initiating a New Conversation: When a user clicks the button to start a new conversation, the method is invoked with the eventName "SHOW".
- Resuming an Existing Conversation: When a user returns to the site and already has an open conversation, Shark identifies this user through the customerId SDE pushed on the page (which should have the same value as the sub in the JWT). Upon identification of an open conversation for the user, the method is triggered with the eventName "OPEN".
Here are the next chapters for the document:
7. Retrieving Domains
Ensure that domain names are not hardcoded.
8. Non-Authenticated Consumer Identity
To acquire a JWT valid for 20 years, make a call to the following LP API endpoint:
POST https://$LP_IDP/api/account/$LP_ACCOUNT/signup
The API call will return a JWT that can then be used to initiate a websocket connection.
9. Authenticated Consumer Identity
Trigger Implicit/Code Flow setup on the LE2.0 account by making a call to the following LP API endpoint with a JWT/authCode:
POST https://$LP_IDP/api/account/$LP_ACCOUNT/authenticate
header:
Content-Type: application/json
body:
{"authCode" : "'$LP_EXT_JWT'"}
This request establishes an authenticated identity based on the external identity provided and returns a JWT for websocket connection.
10. Routing
Utilize 'customerType' or 'companyBranch' SDEs, mapped to skills in Houston, for routing purposes.
11. Web Messaging - Navigating on Web Pages
Store the JWT in session storage to indicate that a messaging session has been initiated. Upon refresh and navigation between pages, the messaging window will resume conversation accordingly. Additionally, load the current conversation history upon page load.
12. Keeping the WebSocket Alive
To prevent the server from closing idle connections, Brand should send a message every 60 seconds. This can be achieved through Websocket Ping/Pong Mechanism or by using the GetClock request.
Below is a sample code and API for Keep Alive to ping server when user is idle:
- Step #1: Define a variable to hold the keep-alive timer in the global (or otherwise accessible) scope:
var socketKeepAliveTimer = null;
- Step #2: Add the below code to the "body.changes.forEach" method inside the "handleOpenedSocket" method:
clearInterval(socketKeepAliveTimer);
socketKeepAliveTimer = null;
socketKeepAliveTimer = setInterval(function() {
socket.request("GetClock").then(resp => {
$("#log").append("Time is " + resp.body.currentTime + "\n");
});
}, 60000);
The inclusion of the then call to log the time is discretionary and primarily intended for debugging and demonstrative purposes. It's also essential to ensure that the interval is cleared when the socket is closed, typically handled in the "onCloseSocket" method.
Regarding best practices:
- For web usage: It's recommended to maintain the socket open while the consumer's window/conversation is in focus. When the consumer is no longer in focus, you can choose to halt the keep-alive process, which will result in the closure of the socket.
- For in-app usage: Keep the websocket active as long as the messaging view remains open. If the user navigates to other sections of the app, it's advisable to discontinue the keep-alive mechanism.
13. Push Notifications with the API
Push Notifications will trigger when the websocket is closed.
14. Tying Conversation to the web session
To establish a connection between a conversation and a web session, it's necessary to include conversation context and campaign information in the conversation request. Here's how to structure the cm.ConsumerRequestConversation
request:
javascript:
{
"kind": "req",
"type": "cm.ConsumerRequestConversation",
"id": "<request id>",
"body": {
"conversationContext": {
"type": "SharkContext",
"visitorId": "<svid from the method object>",
"sessionId": "<ssid from the method object>",
"interactionContextId": "<scid from the method object>"
},
"campaignInfo": {
"campaignId": "<cid from the method object>",
"engagementId": "<eid from the method object>"
},
"brandId": "le52821091"
}
}
The necessary web session data can be found on the page when a method is triggered upon button click. This method receives a data object containing multiple attributes:
svid
→ mapped to visitor idssid
→ mapped to session idscid
→ mapped to interaction context id
15. CORS - cross domain support
Cross-Origin Resource Sharing (CORS) is crucial for overcoming the limitations imposed by the same-origin policy in web browsers. CORS allows a web page to access resources from different origins. When making a cross-origin request, the browser sends an Origin header with the current domain value (http://domain.com
). The server then checks whether the origin header is within an allowed list and returns a header access-control-allow-origin
with the allowed value.
16. Getting timestamp of each message
The timestamp of each message can be obtained from the serverTimestamp
attribute in the ms.MessagingEventNotification
. This timestamp is provided in UTC format.
17. CoBrowse
To enable CoBrowse and CoApp_Integration features on the account, follow these steps:
- Set CoBrowse Feature in Client Properties:
- Include the
CO_BROWSE
feature in the clientProperties
in the initConnection
request.
- Start a conversation and trigger a CoBrowse invitation.
- Handle CoBrowse events on the consumer side:
- Upon receiving an invitation, trigger the
cobrowseOffered
event. - If the consumer accepts the invitation, trigger the
cobrowseAccepted
event to start the CoBrowse session. - If the consumer rejects the invitation, trigger the
cobrowseDeclined
event.
Remember to set Agent permissions in the Users tab, specifically in the agent profiles.
18. Visitor Presence
- Set the site setting
le.agent.messaging.OnlinePresence
to either true or false and define the interval for the events being sent. By default, it is set to 60 seconds. - Implement by sending
ms.PublishEvent
with the following payloads:
javascript:
{"id":"0","type":"ms.PublishEvent","body":{"dialogId":"","event":{"type":"ChatStateEvent","chatState":"ACTIVE"}}}
javascript:
{"id":"0","type":"ms.PublishEvent","body":{"dialogId":"","event":{"type":"ChatStateEvent","chatState":"INACTIVE"}}}
19. Consumer History API
The LiveEngage Consumer Messaging History API allows retrieval of up to 13 months of historical conversation data, both metadata and content, for a specific consumer.
Conversations Metadata
This method retrieves a list of a consumer's conversations' metadata. The retrieved data can be filtered by time, range, and state.
GET Endpoint:
Headers:
- Authorization: Bearer
<bearer>
- Content-Type: application/json
Sample Data:
javascript:
{ "_responseMetadata": { "count": 3, "self": { "rel": "self", "href": "https://va.msghist.liveperson.net/messaging_history/api/account/72772122/conversations/consumer/metadata/search?state=close&startFrom=1509641550327&startTo=1517447640000&limit=100&offset=0&sort=start:desc" }, "shardsStatusResult": { "partialResult": false } }, "conversationHistoryMetadataRecords": [ { "convId": "b1d2aed7-73a1-4322-b12c-fe48cb828ca9", "participants": [ { "id": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "role": "CONSUMER" }, { "id": "99f45a46-a56f-54ed-9407-7c8d38abdd83", "role": "CONTROLLER" } ], "state": "CLOSE", "startTs": 1517419126876, "endTs": 1517419268099 }, { "convId": "aad330a5-02cf-48dd-9817-8d5036256ada", "participants": [ { "id": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "role": "CONSUMER" }, { "id": "99f45a46-a56f-54ed-9407-7c8d38abdd83", "role": "CONTROLLER" }, { "role": "ASSIGNED_AGENT" } ], "state": "CLOSE", "startTs": 1517419058167, "endTs": 1517419070964 }, { "convId": "ffe692c2-692b-4ba2-8569-4091f6784c63", "participants": [ { "id": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "role": "CONSUMER" }, { "id": "99f45a46-a56f-54ed-9407-7c8d38abdd83", "role": "CONTROLLER" }, { "role": "ASSIGNED_AGENT" } ], "state": "CLOSE", "startTs": 1517418814135, "endTs": 1517418844847 } ]}
Conversations Content
GET Endpoint:
Headers:
- Authorization: Bearer
<bearer>
- Content-Type: application/json
Sample Data:
javascript:
{ "messageEventRecords": [ { "sequence": 0, "originatorId": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "serverTimestamp": 1517419126983, "event": { "type": "ContentEvent", "contentType": "text/plain", "message": "Virtual Assistant : Hi Ruth, how can I help you today?" } }, { "sequence": 1, "originatorId": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "serverTimestamp": 1517419126988, "event": { "type": "ContentEvent", "contentType": "text/plain", "message": "CM : human agent" } }, { "sequence": 2, "originatorId": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "serverTimestamp": 1517419126993, "event": { "type": "ContentEvent", "contentType": "text/plain", "message": "Virtual Assistant : To better assist you, please wait a moment while I bring in a Customer Care Professional." } }, { "sequence": 3, "originatorId": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "serverTimestamp": 1517419188777, "event": { "type": "ContentEvent", "contentType": "text/plain", "message": "hi\n" } }, { "sequence": 4, "originatorId": "01f2e5520aaaa655888be2dc57aad5833db321e559db277a13cca56966d9aff8", "serverTimestamp": 1517419251877, "event": { "type": "ContentEvent", "contentType": "text/plain", "message": "hi\n" } } ]}
20. Identifying Conversation Close Reason
When a conversation is closed, and you receive an ExConversationChangeNotification, the payload will contain a closeReason, as in the example below:
javascript:
{ "subscriptionId": "5d3def17-9ac4-48b2-a2e3-00fd13b37011", "changes": [ { "type": "DELETE", "result": { "convId": "41d33e78-9701-4edd-a569-01dfb6c0f40a", "conversationDetails": { "skillId": "-1", "participants": [ { "id": "d51ce914-97ad-4544-a686-8335b61dcdf3", "role": "CONSUMER" }, { "id": "393c6873-756d-54af-86e1-8795d57eba14", "role": "ASSIGNED_AGENT" } ], "state": "CLOSE", "closeReason": "CONSUMER", "startTs": 1517000827241, "endTs": 1517007161967, "metaDataLastUpdateTs": 1517007161968, "firstConversation": false, "ttr": { "ttrType": "NORMAL", "value": 3600 }, "context": { "type": "MobileAppContext", "lang": "en-US", "clientProperties": { "type": ".ClientProperties", "appId": "com.liveperson.mmanguno.upgradetest23_30", "ipAddress": "172.26.138.213", "deviceFamily": "MOBILE", "os": "ANDROID", "osVersion": "27", "integration": "MOBILE_SDK", "integrationVersion": "3.0.0.0", "timeZone": "America/New_York", "features": [ "PHOTO_SHARING", "CO_APP", "AUTO_MESSAGES", "RICH_CONTENT", "SECURE_FORMS" ] } }, "__myRole": "ASSIGNED_AGENT" }, "lastContentEventNotification": { "sequence": 14, "originatorClientProperties": { "type": ".ClientProperties", "ipAddress": "172.26.138.213" }, "originatorId": "89476943.282467514", "originatorPId": "393c6873-756d-54af-86e1-8795d57eba14", "originatorMetadata": { "id": "393c6873-756d-54af-86e1-8795d57eba14", "role": "ASSIGNED_AGENT", "clientProperties": { "type": ".ClientProperties", "ipAddress": "172.26.138.213" } }, "serverTimestamp": 1517007156126, "event": { "type": "ContentEvent", "message": "you said Yes this is mark!", "contentType": "text/plain" }, "dialogId": "41d33e78-9701-4edd-a569-01dfb6c0f40a" } } } ]}
In this payload, the "closeReason" field indicates the reason for the conversation closure. In the example above, the close reason is "CONSUMER," suggesting that the consumer initiated the closure of the conversation.
21. Structured Content
The Brand will need to render the received JSON object on the client side to produce the UI, as the window is in full control of the Brand when using the API.
22. API Error Codes
The Brand is responsible for error handling. The API has a list of error codes. In case they occur, the Brand can translate those errors into a more user-friendly display for the consumer. Error codes include:
- 200 OK
- 400 Bad Request
- 401 Authorization Error
- 404 Not Found
- 405 Method Not Allowed
- 500 Internal Server Error
- 503 Service Unavailable
- 4401, 4407: Issues with the JWT in authenticated/unauthenticated scenarios
23. Resume with the API - Limitation
Sometimes, a method on the page can be triggered with the eventName "OPEN," indicating that a user can open a conversation that needs to be resumed. However, once opening a websocket, subscribing to conversation metadata, and receiving a notification, there might be no conversationId, indicating that there is no open conversation.
AMEX Issue:
If the eventName "OPEN" is received, the messaging window is opened, skipping the bot, and an attempt is made to resume, but there is nothing to resume. The window is already open without the ability to start a conversation as expected.
Recommended Workaround:
Don't trust the page event without checking the conversation metadata notification and confirming that a conversationId to resume exists. Only then open the window.
24. PCI Forms with the API - Limitation
PCI Forms are not supported with the Messaging Window API (as well as Chat API/SDK). Customers would need to implement their user experience (such as window slide-out) to display the forms and deliver them to their backend in a PCI-compliant way. The Agent Widget can be used to retrieve information from the backend and display it to an agent.
25. Pre-chat Surveys with Messaging - Limitation
Pre-chat surveys are not supported in messaging. Therefore, if used by the brand, they cannot be submitted with the API.
Possible Workarounds:
- Pass any pre-chat lines/external bot transcript as the first message from the user. Due to the known 5K char limitation on a line, the text may need to be split into several lines as needed.
- Pass known conversation 'intent' to LP through authentication as an authenticated SDE, so the agent can have visibility.
26. Exit Surveys - Limitation
Messaging supports only one exit survey - CSAT.
Possible Workaround:
Pass questions and answers from the page and set them into an SDE. Unauthenticated SDEs are not supported at this time, so reliance on the Analytics team to provide the needed reporting and tie leveraged SDEs to the conversation is necessary.
javascript:
lpTag.sdes = lpTag.sdes||[];lpTag.sdes.push( { "type": "searchInfo", "keywords": [ "es_q1|Question|Answer", "es_myca_q2|How satisfied were you with this conversation?|Very Satisfied", "es_myca_q2a|How satisfied were you with this conversation?|Very Satisfied", // Add more questions and answers as needed ] });
27. UMS - Limitations
- Server Connection Limit:
server.connections.max.limit = 50K
, server.connection.percentage.alert = 60
- Connections per Brand:
brand.consumer.connections.max.limit = 20K
(open concurrent connections per brand), brand.agent.connections.max.limit = 5K
, brand.app.connections.max.limit = 100 (app JWT)
- Server Conversations Number Limit:
server.conversations.max.limit = 1200000000
28. Identifying Offline Hours - Limitation
The API doesn't support expected wait time in messaging. Also, TTR is not per skill and can only be obtained after the conversation has been created.
Workaround:
Identify if the time now is after business hours to not make a request for a new conversation for a particular skill (LOB).
29. Retrieving Agent Profile - Limitation
Messaging Window API provides Agent PID. The AgentProfile API can be used to retrieve agent info.
30. Getting Updated Skill after Transfer - Limitation
If a brand implemented several custom exit surveys and has a need to display a relevant exit survey according to the latest skill of the conversation, the API doesn’t provide skill info once the agent is assigned.
Workaround:
Get the skill on the agent side (agent widget) and have it available on the backend for the window to retrieve when the exit survey needs to be identified.
31. Client Properties - Limitation
It's possible to send client properties for both flows (authenticated and not). The client can send information about its type, such as mobile, desktop, browser type, OS version, etc.
After the authentication step, when opening a websocket, client properties can be sent. However, even though the documentation specifies multiple available attributes, only a few of these are seen in the Agent view in LE2.0.
javascript:
socket.initConnection({}, [{ "type": ".ams.headers.ConsumerAuthentication", "jwt": jwt },{ "type": ".ams.headers.ClientProperties", "deviceFamily": "DESKTOP", "os": "WINDOWS", "Browser": "", "appId": "", "appVersion": "", "deviceModel": "", "deviceManufacture": "", "timeZone": "", "browserVersion": "", "integrationVersion": "", "integration": "WEB_SDK", "osName": "", "osVersion": ""}]);
Example:
javascript:
{ type: '.ClientProperties', appId: 'MPP20', appVersion: 'myAppVersion', ipAddress: '192.100.100.2', deviceFamily: 'MOBILE', os: 'IOS', osVersion: '10.1.2 MPP20', osName: 'myOsName', integration: 'MOBILE_SDK', integrationVersion: 'myIntegrationVersion', timeZone: 'myTimeZone', deviceManufacture: 'myDeviceManufacture', deviceModel: 'myDeviceModel', features: []}
32. LP CSAT - Limitation
LP calculates CSAT according to 4 & 5 ratings. However, some systems like AMEX calculate only according to the top score (5 score), so they had to adjust and send all their 4 ratings as 3.
33. Identifying Estimated Wait Time - Limitation
Messaging Window API doesn’t provide estimated wait time. Brands that would like to present that info to the consumer don’t have an out-of-the-box approach.
Possible Workaround - AMEX Solution:
Create a backend service that runs every 90 seconds, pulls conversations that started last 5 minutes, and calculates the estimated wait time.
34. Identifying Agent Availability - Limitation
Currently, LE2.0 always shows a button for messaging, even if all agents are at full capacity. This causes visitors to request messaging conversations when agents are not available to respond, leading to long wait times and high abandonment rates. In AMEX's case, visitors are invited to start a conversation, which impacts the user experience negatively.
This solution is designed to solve this problem by identifying agent availability on the backend and showing the button accordingly on the page.
35. Keep Alive
- Need to open the socket on each page and send a keep-alive every minute on each.
- When you close a tab, the associated socket closes. If you need the conversation to show on every tab, you need a socket on each.
- Use the blur event to identify that the user is not active.
- After X minutes of inactivity, stop sending keep-alive (if the user forgot to close multiple tabs and left).
- Can store JWT in session storage to indicate that a messaging session has been created. On refresh and navigating between pages, the messaging window resumes the conversation accordingly on the page. Load current conversation history on page load.
36. Agent Public Profile API
The Agent Public Profile API documentation might not be easily accessible, as it's sometimes buried within other references. If you can't find it in the expected location, try searching for the official API name, "Agent Public Profile."
Currently, the documentation has buried the reference at the bottom of the following link:
- Messaging Window API - Overview | LivePerson Developers
- It may well be moved in our documentation, so if you don’t find on this page anymore, do a search with the official API name, “Agent Public Profile
- Documentation also doesn’t provide the service name (14/12/2018) for this API, so the brand may get confused
https://${accdnDomain}/api/account/${accountId}/configuration/le-users/users/${pid}
Example:
1. Retrieve the domain for the Agent Public Profile API. Use the LivePerson Domain API to retrieve this information by providing the following service name:
- acCdnDomain
- Note: this is case sensitive.
- Make this call in your app once for that service:
- http://api.liveperson.net/api/account/78722355/service/acCdnDomain/baseURI.json?version=1.0
- The JSON response will be:
{
"service": "acCdnDomain",
"account": "78722355",
"baseURI": "accdn.lpsnmedia.net"
}
- NOTE: LP reserves the ability to rename baseURI or change due to being a SaaS with the security model it goes with
- So, don't skip that step and don't hard code the baseURI for the Agent Public Profile API call (this is true for all our APIs)
2. Leverage for each new LE participant to the conversation the Agent Public Profile API:
- https://$LP_ACCDNDOMAIN/api/account/$LP_ACCOUNT/configuration/le-users/users/$LP_AGENT_PID
- $LP_ACCDNDOMAIN: the baseURI for the "acCdnDomain", namely, at this point in time, your app will set it to accdn.lpsnmedia.net
- $LP_AGENT_PID: agent’s PID
- when the participant array gets updated to include the agent's identifier when accepting the conversation
- when a user in LE sends back an actual message
- Update to the participant array is the earliest event you can query the Agent Public Profile API:
- ex:
- remember to check the role, so that you don't try to call the Agent Public Profile API for our Automatic System Message bot - identifiable with the CONTROLLER role
- When an actual message is sent from our platform, it can be an agent or a manager (or the Automatic System Message bot)
- when this happens, the originatorId and originatorMetadata for the role will tell you what UI to render (message from Agent, from Manager that has joined the conversation, if you want to have a different UI)
3. RESPONSE - The Agents Public Profile retrieved will look like this:
{"employeeId":"XXX",
"nickname":"ZZZ",
"fullName":"YYYY"}
NOTE: As EmployeeId is exposed, it is recommended not putting any sensitive information in the employeeId.
4. Gap: the Agent Public Profile API doesn’t retrieve the url of the LE User picture a possible work around until we have a publicly supported api is for the brand to build an Internal DB (on the device for in-app? Server one?) with the originatorId as primary key - which is not ideal.
37. Receive AUTO messages
javascript:
{ "id":"0", "type":"InitConnection", "headers":[ { "type":".ams.headers.ClientProperties", "deviceFamily":"DESKTOP", "os":"WINDOWS", "features":[ "AUTO_MESSAGES" ] }, { "type":".ams.headers.ConsumerAuthentication", "jwt":"..." } ]}
javascript:
{"Id":"0","type":"InitConnection","Headers":[{"type":".ams.headers.ConsumerAuthentication","jwt":"eyJraWQiOiIwMDAwMSIsInR5cCI6IkpXVCIsImFsZyI6IlJTMjU2In0.eyJzdWIiOiJjZTBjMDc2YS1iOGNiLTQ0YzMtOWM5MS0xMGZkZWFmNjU0NTMiLCJhdWQiOiJhY2M6Mzc2NDQ3OTEiLCJpc3MiOiJodHRwczpcL1wvaWRwLmxpdmVwZXJzb24ubmV0IiwiZXhwIjoxOTQ3Njk0ODAxLCJpYXQiOjE1Mzc0NTQ3NjEsImp0aSI6IjBjYTY4ODMwLTliOWQtNDc5MC05OWMwLTNjNjlkYmU0ZmMzYSJ9.Eo2s2om1hauoLcvA_kPsYRf765YWnGPNUxLlki-yA0hjcIpMHu8OB9aAXenqJpu1YUFSS1FRUVAvlcaTvkEtD0SqaoPJpTbNLZOOA9hCMT4bWRMLQLn6B8Wmt-I1cKl93t7KyNCYHNJjjoijwzt1X4ywM23zK82k2P12uWwDKL_XuGeyFG5Q-kBczlm05Oa6utQM3e3gKDzADtsdI6PbdAjJmEFxqy7QEEtohE-lSrAEaluWga-h6MaSt72_OvcYV64lnjaBiLu1b8M_5s56smozo_axTPcjm5DjpDS62XGikr7mpXVs_NF6XDmIgm4S-RHdFki3WNtIohRlEtSW8A"},{"type":".ams.headers.ClientProperties","deviceFamily":"DESKTOP","os":"OSX","ipAddress":"127.0.0.1","integration":"WEB_SDK","Browser":"firefox","deviceManufacture":"Apple","deviceModel":"iPhone6s","Features":["AUTO_MESSAGES"]}]}
38. Photo Sharing
The Messaging Window API should support photo sharing. Please refer to the documentation provided at:
Below is a concise summary, but it's crucial to consult the documentation for detailed implementation and confirmation of its compatibility with your team's requirements:
javascript:
{"kind":"req","id":"3","body":{"fileSize":1000,"fileType":"PNG"},"type":"ms.GenerateURLForUploadFile"}
- Begin by sending a request to GenerateURLForUploadFile. This will return a relativePath, temp_url_sig, and temp_url_expires.
- Proceed to upload the file to the Swift server at https://{{swiftURL}}{{relativePath}}?temp_url_sig={{temp_url_sig}}&temp_url_expires={{temp_url_expires}}
- Finally, send a message containing the base64 encoded image thumbnail and the relativePath:
javascript:
{"kind":"req","id":"3","body":{"dialogId":"{{conversationId}}","event":{"type":"ContentEvent","message":{"caption":"LivePerson logo","relativePath":"{{relativePath}}","fileType":"PNG","preview":"data:image/png;base64,{{data}}"},"contentType":"hosted/file"}},"type":"ms.PublishEvent"}
39. Unauthenticated Messaging with Messaging Window API
To engage in unauthenticated messaging using the Messaging Window API, you'll first need to make a call to the incognito service provided at:
https://va-a.idp.liveperson.net/api/account/79316966/anonymous/authorize
This call will return a token in the response. You'll then use this token to authenticate your call to:
https://va-a.idp.liveperson.net/api/account/79316966/app/750963030/authenticateThis second call will provide you with an lpToken.
Here's an example of an LP window setup:
https://lpgithub.dev.lprnd.net/ve-le-lp/unauthenticatedMessaging/blob/master/src/unauth.js
Additionally, you can find sample code to connect to an account here:
By following these steps, the client ensures a smooth and efficient conclusion to the conversation, allowing for proper closure and, if applicable, the collection of valuable feedback.