Skip to content

02 Get your data in /Integrations

Crisp

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

  1. C CrispTickets, 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 Crisp conversation as one conversation: what the visitor wrote and what your operators and bots replied, across chat, email and the other channels connected to your inbox. You run it on your side: a small script pulls from the Crisp REST API and posts each message to the universal API.

  • The owner of your Crisp workspace: only owners can generate a website token.
  • Your website token (an identifier and a key) and your website ID.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

Crisp’s own steps are in Website token.

  1. Open the Crisp Dashboard and go to Settings > Workspace Settings > Advanced configuration.
  2. In the API Token section, click Generate Token.
  3. Copy the token identifier and key. They are shown only once.
  4. Copy your website ID from Settings > Workspace Settings > Setup Instructions.

Authenticate with Basic auth (the identifier as the username, the key as the password) and add the header X-Crisp-Tier: website to every request.

A website token reaches one workspace and allows 10,000 requests a day. For several workspaces or a higher quota, Crisp offers plugin tokens through its Marketplace (authentication).

List conversations updated in a time range. The page number is part of the path:

Terminal window
curl --user "$CRISP_TOKEN_ID:$CRISP_TOKEN_KEY" -H "X-Crisp-Tier: website" \
"https://api.crisp.chat/v1/website/$CRISP_WEBSITE_ID/conversations/1?per_page=50&order_date_updated=1&filter_date_start=2026-10-01T00:00:00.000Z&filter_date_end=2026-10-26T00:00:00.000Z"
  • per_page is between 20 and 50.
  • filter_date_start and filter_date_end take ISO 8601 dates and apply to when the conversation was last updated.
  • Each conversation has a session_id and a people_id.

For each conversation, get its messages with GET /v1/website/{website_id}/conversation/{session_id}/messages. It returns the latest batch of messages. To page back to older ones, pass the oldest timestamp you have as timestamp_before.

Rate limits, per Crisp: on top of the website token’s daily quota, the API applies global and per-route limits. Going over returns 429 Too Many Requests or 420 Enhance Your Calm (rate limits).

Dimension LabsCrispNotes
textmessage contentOnly messages whose type is text. Skip note messages: those are private notes for your team. Files, pickers and events are skipped too.
userIdconversation people_idCrisp’s internal id for the contact, on every message of the conversation. Fall back to the sessionId.
incomingmessage fromtrue when from is user (the visitor). Operators and bots are operator.
sessionIdconversation session_idPrefix it, for example crisp-session_700c65e1-....
conversationIdconversation session_idSame value as sessionId.
dimensionlabs_timestampmessage timestampConvert with toMillis.
platformJsonmessage origin, automated; conversation state, inbox_id, meta.segmentsautomated is true for bot messages.

Personal data. Don’t send the nickname, email, phone, address or ip from the conversation’s meta. Messages can hold the same details: redact them before you send. See PII redaction.

// crisp.mjs. Environment: CRISP_WEBSITE_ID, CRISP_TOKEN_ID, CRISP_TOKEN_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 base = `https://api.crisp.chat/v1/website/${process.env.CRISP_WEBSITE_ID}`;
const headers = {
Authorization: 'Basic ' + Buffer.from(`${process.env.CRISP_TOKEN_ID}:${process.env.CRISP_TOKEN_KEY}`).toString('base64'),
'X-Crisp-Tier': 'website',
};
// Retries Crisp's rate limits (429 and 420) and server errors (5xx), honoring Retry-After.
async function get(url) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { headers });
if (res.ok) return res.json();
const retry = res.status === 429 || res.status === 420 || res.status >= 500;
if (!retry || attempt === 5) throw new Error(`Crisp returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
// Reads a conversation's messages, newest batch first, back to `since`.
async function messagesSince(sessionId, since) {
const all = [];
for (let before = null; ; ) {
const res = await get(`${base}/conversation/${sessionId}/messages${before ? `?timestamp_before=${before}` : ''}`);
const batch = res.data ?? [];
if (!batch.length) break;
all.push(...batch);
const oldest = Math.min(...batch.map((m) => m.timestamp));
if (oldest < since) break;
before = oldest;
}
return all;
}
// Sends every message written at or after `since` (Unix ms). Returns the new `since`.
export async function syncCrisp({ since, historical = false }) {
const range = `filter_date_start=${encodeURIComponent(new Date(since).toISOString())}&filter_date_end=${encodeURIComponent(new Date().toISOString())}`;
let newest = since;
for (let page = 1; ; page++) {
const conversations = (await get(`${base}/conversations/${page}?per_page=50&order_date_updated=1&${range}`)).data ?? [];
if (!conversations.length) break;
for (const c of conversations) {
const id = `crisp-${c.session_id}`;
for (const m of await messagesSince(c.session_id, since)) {
const ts = toMillis(m.timestamp);
if (m.type !== 'text' || typeof m.content !== 'string' || ts < since || !m.content.trim()) continue;
await sendToDimensionLabs({
text: m.content,
userId: c.people_id || id,
incoming: m.from === 'user',
sessionId: id,
conversationId: id,
dimensionlabs_timestamp: ts,
platformJson: { origin: m.origin, automated: m.automated ?? false, state: c.state, inbox_id: c.inbox_id, segments: c.meta?.segments },
}, { historical: historical || ts < Date.now() - HOUR });
newest = Math.max(newest, ts);
}
}
}
return newest;
}

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

  • History first: node run.mjs 2025-01-01 sends every message since that date to /trackhistorical. 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.

Each conversation costs at least one request, so the website token’s 10,000 requests a day limit how much history one day’s run can load. If a history load stops at the quota, run it again the next day with the same date: what was already sent is deduplicated. You can also upload a file of exported conversations with one row per message (check it with the file checker), then run a backfill.

Checked against Crisp’s documentation on 2026-10-26.