**COMING SOON — BETA RELEASE NOT FOR PRODUCTION USE** The Headless SDK is currently under active development and is not yet available for beta use. We’ve published this documentation early to help you familiarize yourself with the SDK and prepare for the upcoming beta release.
Once the Headless SDK is available for beta, we’ll notify brands through our release notes.
When released as beta:
- The beta release is intended for early evaluation purposes only and is not recommended for production use.
- The SDK may contain bugs, errors, or incomplete or inaccurate information.
- APIs, features, and behavior are subject to change as we continue to develop and improve the SDK.
- Do not deploy or rely on the beta release in production environments.
Please note: The documentation, APIs, features, and examples may change before and during the beta period.
Reproduces the Agent Workspace's conversation-list filters (Ongoing, In-Queue, Overdue, Closed, Idle) on top of @liveperson/headless-sdk. The SDK has no filters API. Each filter is a predicate your app runs over a locally-maintained conversation store.
Clients: all examples use only the public MessagingClient API — client.on({ callback }) for events and client.dispatch({ action, value }) for actions. dispatch() returns a Promise; inline references below omit await for brevity, but always await it in real code.
Prerequisite: live conversation store
What it does: Keeps a local, up-to-date map of the agent's open and recently-closed conversations. Every real-time filter below (Ongoing, In-Queue, Overdue, Idle, Closed) is computed by running a predicate over this map — none of them are separate SDK queries.
Instructions:
const conversations = new Map<string, Conversation>();
client.on({
callback: (event) => {
if (event.type === 'conversations/update') {
const { type, conversation } = event.value;
if (type === 'delete') {
conversations.delete(conversation.id);
} else {
conversations.set(conversation.id, conversation);
}
}
},
});
Build the map from conversations/update events after connect. MessagingClient exposes no snapshot API, so the store is populated entirely from the live event stream.
⚠️ Note: the recently-closed retention window is exposed as
session.permissions.configuration.recentlyClosedConversationDays(site settingle.agent.recentlyClosedConversationDays, a plainnumberbetween 2–14). The SDK does not clamp, validate, or enforce it — it's a passthrough of whatever the site setting contains, so you'll need to enforce the time range yourself by not saving/showing conversations older than the configured retention.
Filter: Closed
What it does: Conversations that are resolved and marked closed (by agent, consumer, or system timeout), including post-survey. Agent Workspace shows conversations closed in the last 2 days by default, admin-configurable up to 14
Instructions:
function isClosed(conversation) {
return conversation.stage === 'CLOSE';
}
- Live close, or reacting to a close:
client.dispatch({ action: 'conversation/close', value: conversationId }). would trigger aconversations/updateupsert withconversation.stage === 'CLOSE'. Dispatching close on an already-closed conversation still sends the frame (there's no cached-state short-circuit), so dedupe in-app if you can close from multiple UI paths. - A
startrange wider than the account's configured retention returns nothing past the boundary and throws no error, so validate the range client-side if you want to warn the user.
⚠️ Note: if the conversation has an open post-survey dialog,
conversation.stagewill beOPEN, so you'll need to add this case to the condition to include it in this filter.
Filter: In-Queue
What it does: Open conversations waiting to be routed, not currently assigned to any agent.
Instructions:
function isInQueue(conversation) {
return conversation.stage === 'OPEN' &&
!conversation.mainDialog.participants.some((p) => p.role === 'ASSIGNED_AGENT');
}
- Rings (
conversation/ringevents,{ conversationId, skillId }) are a sub-state of In-Queue, not a separate filter — answer withclient.dispatch({ action: 'conversation/accept', value: { conversationId } })orclient.dispatch({ action: 'conversation/reject', value: conversationId }). A rejected/expired ring leaves the conversation In-Queue for the next offer. -
client.dispatch({ action: 'conversation/to-queue', value: conversationId })removes the currentASSIGNED_AGENT, moving a conversation back to In-Queue without changingstage.
Filter: Overdue
What it does: Open, assigned conversations where the agent has breached the SLA response time.
Instructions:
function isOverdue(conversation: Conversation): boolean {
return conversation.stage === 'OPEN' &&
conversation.sla.time > 0 &&
conversation.sla.time < Date.now();
}
-
conversation.sla.timeis a plainnumber(nevernull) with two sentinel values:0means the conversation is closed and the SLA no longer applies,-1means the SLA time is unknown/unset. Any other value is an epoch-ms deadline —sla.time > 0filters out both sentinels in one check. -
conversation.sla.typeis'manual' | 'auto'— an agent-set manual SLA (viaconversation/set-manual-sla) still counts as Overdue once its deadline passes; only the Idle filter below excludes'manual'. - The response-time targets used to compute this deadline server-side (
session.permissions.configuration.defaultResponseTime/.urgentResponseTime/.prioritizedResponseTime, each{ value: number; unit: string }) are informational only — the backend already factors them intoconversation.sla.timebefore it reaches the SDK. Don't recompute the deadline from them; useconversation.sla.timedirectly.
Filter: Idle
What it does: Open, assigned conversations where the agent sent the last message but the consumer hasn't responded within the configured "Time-to-Idle." Lets agents filter out stale conversations.
Instructions:
function getTimeToIdleMs(skillId: number | undefined, configuration: AgentConfiguration): number {
const perSkill = configuration.smartCapacityPerSkill.smartCapacityParametersList.find(
(override) => override.skillId === skillId,
);
const timeToIdleSec = perSkill?.smartCapacityFreshAgentPendingTimeSec ?? configuration.smartCapacityDefaultSec;
return timeToIdleSec * 1000;
}
function isIdle(conversation: Conversation, configuration: AgentConfiguration): boolean {
const timeToIdleMs = getTimeToIdleMs(conversation.skillId, configuration);
const lastMessage = conversation.lastMessage;
let lastAgentMessageTime;
if (lastMessage?.originatorRole === 'ASSIGNED_AGENT' || lastMessage?.originatorRole === 'MANAGER') {
lastAgentMessageTime = lastMessage?.serverTimestamp;
} else {
const lastAgentMessage = [...conversation.mainDialog.messages]
.reverse()
.find((m) => m.participant.role === 'AGENT' || m.participant.role === 'ASSIGNED_AGENT');
lastAgentMessageTime = lastAgentMessage?.sentAt;
}
return conversation.sla.type !== 'manual' &&
conversation.sla.time === -1 &&
(!lastAgentMessageTime || Date.now() - lastAgentMessageTime >= timeToIdleMs)
}
-
conversation.sla.timeis a plainnumber(nevernull):-1means no SLA countdown is active, which is the precondition for a conversation to be eligible as Idle instead of Overdue.0(closed) is excluded implicitly — Idle only applies to open, assigned conversations. -
configurationabove issession.permissions.configuration(AgentConfiguration, from types.ts) — read it once per session, not per conversation. -
Per-skill override —
configuration.smartCapacityPerSkill: parsed from site settingmessaging.smart.capacity.per.skill.definition. Shape:{ smartCapacityParametersList: { skillId: number; smartCapacityFreshAgentPendingTimeSec: number }[] }. Look up the conversation'sskillIdin that list; if present, itssmartCapacityFreshAgentPendingTimeSecis the Time-to-Idle for that skill, in seconds. -
Global default —
configuration.smartCapacityDefaultSec: parsed from site settingmessaging.smart.capacity.fresh.agent.pending.time.sec. Used whenever no per-skill override matches the conversation'sskillId(including when the conversation has noskillId). - Precedence: per-skill override wins over the default. Both values are in seconds — multiply by 1000 before comparing against
Date.now()deltas. - If
messaging.smart.capacity.per.skill.definitionis missing or malformed JSON, the SDK falls back to an emptysmartCapacityParametersList(i.e. every conversation uses the global default) rather than throwing.
⚠️ Note: don't confuse this with
configuration.conversationIdleTimeMinutes(site settingle.agent.messaging.conversationIdleTime.minutes) — that's a separate, currently-unused-by-this-filter setting. The Idle filter's Time-to-Idle is derived solely fromsmartCapacityPerSkill/smartCapacityDefaultSec.
Filter: Ongoing
What it does: Open, assigned conversations that are neither Overdue nor Idle — i.e., the last message was from the consumer, or from the agent within the configured idle time. Part of the default view; cannot be hidden.
Instructions:
function isOngoing(conversation: Conversation): boolean {
return no-other-filter-applies;
}