Skip to content

02 Get your data in /Integrations

Kustomer

Send Kustomer conversations and their messages to Dimension Labs with the universal API.

  1. K KustomerTickets, 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 each Kustomer conversation and its messages from every channel, customer and agent alike. You run it on your side: a small script pulls from the Kustomer API and posts each message to the universal API.

  • A Kustomer admin, to create an API key.
  • A Kustomer API key with roles that allow search, conversation read and message read (for example org.permission.search.create, org.permission.conversation.read and org.permission.message.read).
  • A Dimension Labs API key for a new Universal API source. See Your API key.
  1. In Kustomer, go to Settings > Security > API Keys.
  2. Create a key, give it a descriptive label and choose its API roles.
  3. Copy the key.
  4. Send it as Authorization: Bearer YOUR_API_KEY on every call (authentication and API keys).

Get conversations has no “updated since” filter, so use customer search with a conversation query instead:

Terminal window
curl -X POST "https://api.kustomerapp.com/v1/customers/search?pageSize=100" \
-H "Authorization: Bearer $KUSTOMER_API_KEY" -H "Content-Type: application/json" \
-d '{"queryContext":"conversation","and":[{"conversation_updated_at":{"gte":"2026-10-13T00:00:00.000Z"}}]}'

Search covers objects updated in the past 2 years; older data is in Kustomer’s Archive Search API. Search returns at most 100 pages. To read more, Kustomer suggests repeating the query from the last updated_at value you received. Follow links.next for the next page (pagination).

For each conversation, get messages by conversation: GET /v1/conversations/{id}/messages?pageSize=100&sort=asc.

Rate limits, per Kustomer, are per minute across all of your organization’s API keys: 300 on Professional (legacy), 500 on Business (legacy), 1,000 on Enterprise and 2,000 on Ultimate. Searches by API keys have their own limit of 100 per minute. Going over returns 429 (rate limiting).

Dimension LabsKustomerNotes
textmessage attributes.previewKustomer’s reference returns the message text in preview and doesn’t say whether long messages are shortened, so compare a long email with the app before relying on it.
userIdmessage relationships.customer.data.idKustomer’s internal customer id, on both their messages and the agents’ replies.
incomingmessage attributes.directiontrue when direction is in, false when it is out.
sessionIdconversation idKustomer ids have no colons.
conversationIdconversation idSame value as sessionId.
dimensionlabs_timestampmessage attributes.sentAt, else attributes.createdAtConvert with toMillis.
platformJsonmessage attributes.channel; conversation attributes.status, tags, satisfactionThe customer id lets you join to customer data in Kustomer.

Personal data. Messages can hold names, emails and phone numbers: redact them before you send. See PII redaction.

// kustomer.mjs. Environment: KUSTOMER_API_KEY, DL_API_KEY.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const api = 'https://api.kustomerapp.com';
// Retries Kustomer's rate limits (429) and server errors (5xx), honoring Retry-After when it is sent.
async function call(path, body) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(api + path, {
method: body ? 'POST' : 'GET',
headers: { Authorization: `Bearer ${process.env.KUSTOMER_API_KEY}`, 'Content-Type': 'application/json' },
body: body && JSON.stringify(body),
});
if (res.ok) return res.json();
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Kustomer returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
async function sendConversation(conv, since, historical) {
let newest = since;
for (let next = `/v1/conversations/${conv.id}/messages?pageSize=100&sort=asc`; next; ) {
const res = await call(next);
for (const m of res.data) {
const a = m.attributes;
const ts = toMillis(a.sentAt ?? a.createdAt);
if (ts < since || !a.preview?.trim()) continue;
await sendToDimensionLabs({
text: a.preview,
userId: m.relationships?.customer?.data?.id ?? conv.id,
incoming: a.direction === 'in',
sessionId: conv.id,
conversationId: conv.id,
dimensionlabs_timestamp: ts,
platformJson: { channel: a.channel, status: conv.attributes?.status, tags: conv.attributes?.tags, satisfaction: conv.attributes?.satisfaction },
}, { historical: historical || ts < Date.now() - HOUR });
newest = Math.max(newest, ts);
}
next = res.links?.next;
}
return newest;
}
// Sends every message written at or after `since` (Unix ms). Returns the new `since`.
export async function syncKustomer({ since, historical = false }) {
let newest = since;
let from = since;
for (;;) {
const query = { queryContext: 'conversation', and: [{ conversation_updated_at: { gte: new Date(from).toISOString() } }], sort: [{ conversation_updated_at: 'asc' }] };
let page = '/v1/customers/search?pageSize=100';
let pages = 0;
while (page) {
const found = await call(page, query);
for (const conv of found.data) {
newest = Math.max(newest, await sendConversation(conv, since, historical));
if (conv.attributes?.updatedAt) from = Math.max(from, toMillis(conv.attributes.updatedAt));
}
page = found.links?.next;
if (page && ++pages === 100) break; // search stops at 100 pages: search again from the last update seen
}
if (!page) return newest;
}
}

Search stops at 100 pages, so on a big history load the code searches again from the last updatedAt it saw, as Kustomer suggests. Conversations at the restart point can be sent twice; duplicates are removed automatically.

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

  • History first: node run.mjs 2025-01-01 sends every message since that date to /trackhistorical. Search reaches back 2 years; older conversations need Kustomer’s Archive Search API or a file. Then start a backfill from the Backfill tab.
  • Then every 15 minutes: the scheduled job sends new messages to /track, and they are enriched automatically every day. A message that is more than an hour old when the job sees it goes to /trackhistorical, and needs a backfill.
  • Re-runs are safe: duplicates are removed automatically.

You can also upload a file of exported conversations (check it with the file checker), then run a backfill.

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