Skip to content

02 Get your data in /Integrations

LiveChat

Send LiveChat chats and transcripts to Dimension Labs with the universal API, on a schedule, with history and backfill.

  1. L LiveChatTickets, chats, calls, reviews
  2. ⟳ Your jobPull new · map fields · every 15 min
  3. { } Universal APIPOST /track · /trackhistorical
  4. Dimension LabsRead in full · enriched daily

This brings in your LiveChat chats, one message at a time: what each customer wrote and what your agents and bots replied. You run it on your side with the universal API, using LiveChat’s Agent Chat Web API.

  • A LiveChat account that can sign in to the Developer Console. A token runs with the permissions of the account that created it, so use an account that can see the chats you want.
  • A Personal Access Token with the chats--all:ro scope (or chats--access:ro for only the groups the account belongs to).
  • A Dimension Labs API key for a new Universal API source. See Your API key.

LiveChat’s own steps are in Personal access tokens.

  1. Open the Developer Console and go to Settings > Authorization > Personal Access Tokens.
  2. Select the scopes. For this guide, select chats--all:ro. You can’t change scopes after you create the token.
  3. Select Create token + and copy the Personal Access Token, your Account ID and the Base64 encoded token.

Personal Access Tokens use Basic authentication: the username is your Account ID and the password is the token. Send the Base64 encoded value as Authorization: Basic YOUR_BASE64_TOKEN.

Use the list_archives method. It returns one entry per thread, with that thread’s events (messages) included.

Terminal window
curl -X POST https://api.livechatinc.com/v3.6/agent/action/list_archives \
-H "Authorization: Basic $LIVECHAT_BASE64_TOKEN" \
-H "Content-Type: application/json" \
-d '{"filters":{"from":"2026-10-13T00:00:00.000000+00:00","event_types":{"values":["message"]}},"sort_order":"asc","limit":100}'
  • filters.from and filters.to take RFC 3339 dates with microseconds.
  • sort_order is asc (oldest first) or desc (the default). limit goes up to 100.
  • Each response may include next_page_id. Send it as page_id in the next request, with no filters, limit or sort_order: LiveChat keeps the filters from the first request. A page_id expires after one month.
  • thread.active is true while a chat is still going. The code below skips those threads and picks them up on a later run, once they have ended.
  • LiveChat applies rate limits and returns a “Too many requests” error when you exceed them. Its docs don’t publish the numbers.
Dimension LabsLiveChatNotes
textthread.events[].textOnly events with type message.
userIdthe user in chats[].users with type customerLiveChat’s internal customer id, on every message of the chat. Don’t use the customer’s email or name.
incomingevents[].author_idtrue when the author is a user whose type is customer. Agents and bots have type agent.
sessionIdthread.idOne thread is one conversation.
conversationIdchats[].idGroups every thread of the same chat.
dimensionlabs_timestampevents[].created_atConvert with toMillis.
platformJsonthread.access.group_ids, thread.customer_visit.geolocation.country_code, chats[].properties, thread.propertiesGroup ids are useful for joins by team or brand.

Personal data. Customers often type names, emails and order numbers into chats: redact them before you send. See PII redaction.

// livechat.mjs. Environment: LIVECHAT_BASE64_TOKEN, DL_API_KEY.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const ENDPOINT = 'https://api.livechatinc.com/v3.6/agent/action/list_archives';
const headers = { Authorization: `Basic ${process.env.LIVECHAT_BASE64_TOKEN}`, 'Content-Type': 'application/json' };
const rfc3339 = (ms) => new Date(ms).toISOString().replace('Z', '000+00:00'); // microseconds, as LiveChat asks
// Retries LiveChat's "Too many requests" (429) and server errors (5xx) with a growing pause.
async function post(body) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(ENDPOINT, { method: 'POST', headers, body: JSON.stringify(body) });
if (res.ok) return res.json();
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`LiveChat returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
// Sends every ended chat thread started at or after `since` (Unix ms). Returns the new `since`.
export async function syncLiveChat({ since, historical = false }) {
let body = { filters: { from: rfc3339(since), event_types: { values: ['message'] } }, sort_order: 'asc', limit: 100 };
let newest = since;
let holdAt = Infinity; // the start of the oldest chat that is still going
while (body) {
const page = await post(body);
for (const chat of page.chats ?? []) {
const t = chat.thread;
const started = toMillis(t.created_at);
if (t.active && started > Date.now() - 24 * HOUR) { holdAt = Math.min(holdAt, started); continue; } // send it once it ends
const customer = chat.users.find((u) => u.type === 'customer');
for (const e of t.events ?? []) {
if (e.type !== 'message' || !e.text?.trim()) continue;
const ts = toMillis(e.created_at);
await sendToDimensionLabs({
text: e.text,
userId: customer?.id ?? t.id,
incoming: e.author_id === customer?.id,
sessionId: t.id,
conversationId: chat.id,
dimensionlabs_timestamp: ts,
platformJson: { group_ids: t.access?.group_ids, country: t.customer_visit?.geolocation?.country_code },
}, { historical: historical || ts < Date.now() - HOUR });
}
newest = Math.max(newest, started);
}
body = page.next_page_id ? { page_id: page.next_page_id } : null;
}
return Math.min(newest, holdAt);
}

A chat that is still going is skipped, and the cursor stays at its start, so the next run picks it up once it has ended. Later chats that already ended are sent straight away; when the cursor is held, they come round again on the next run and the duplicates are removed automatically. A chat left open for more than a day is sent as it is, so one forgotten chat can’t hold the cursor back.

Save the code as livechat.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncLiveChat as sync } from './livechat.mjs';.

  • History first: node run.mjs 2025-01-01 sends every chat since that date to /trackhistorical. Then start a backfill from the Backfill tab.
  • Then every 15 minutes: the scheduled job sends ended chats to /track, and they are enriched automatically every day. Because a chat is sent once it ends, the first messages of a chat that ran for more than an hour go to /trackhistorical, and need a backfill.
  • Re-runs are safe: duplicates are removed automatically.

You can also upload a file of exported chats and check it first with the file checker, then run a backfill.

Checked against LiveChat’s documentation on 2026-10-13.