02 Get your data in /Integrations
Crisp
Send Crisp conversations and their messages to Dimension Labs with the universal API.
- C CrispTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- 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.
What you need
Section titled “What you need”- 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.
1. Get API access in Crisp
Section titled “1. Get API access in Crisp”Crisp’s own steps are in Website token.
- Open the Crisp Dashboard and go to Settings > Workspace Settings > Advanced configuration.
- In the API Token section, click Generate Token.
- Copy the token identifier and key. They are shown only once.
- 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).
2. Pull new conversations
Section titled “2. Pull new conversations”List conversations updated in a time range. The page number is part of the path:
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_pageis between 20 and 50.filter_date_startandfilter_date_endtake ISO 8601 dates and apply to when the conversation was last updated.- Each conversation has a
session_idand apeople_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).
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | Crisp | Notes |
|---|---|---|
text | message content | Only messages whose type is text. Skip note messages: those are private notes for your team. Files, pickers and events are skipped too. |
userId | conversation people_id | Crisp’s internal id for the contact, on every message of the conversation. Fall back to the sessionId. |
incoming | message from | true when from is user (the visitor). Operators and bots are operator. |
sessionId | conversation session_id | Prefix it, for example crisp-session_700c65e1-.... |
conversationId | conversation session_id | Same value as sessionId. |
dimensionlabs_timestamp | message timestamp | Convert with toMillis. |
platformJson | message origin, automated; conversation state, inbox_id, meta.segments | automated 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.
4. Send it
Section titled “4. Send it”// 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;}5. Run it
Section titled “5. Run it”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-01sends 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.
Crisp reference
Section titled “Crisp reference”- Authentication
- Website token
- Rate limits
- REST API reference (List conversations, Get messages in conversation)
Checked against Crisp’s documentation on 2026-10-26.