Skip to content

02 Get your data in /Integrations

Freshchat

Send Freshchat chat transcripts to Dimension Labs with the universal API.

  1. F FreshchatTickets, 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 Freshchat chats: what each customer wrote and what your agents and bots replied, with one session per interaction (from the first message until the chat is resolved). You run it on your side: a small script pulls the Chat-Transcript raw report from the Freshchat API and posts each message to the universal API.

  • A Freshchat admin, to generate an API key.
  • Your Freshchat API key and account URL.
  • Node’s csv-parse package: npm install csv-parse. The report arrives as CSV.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

Freshchat’s own steps are in its API reference, under Authentication.

  1. In a standalone Freshchat account, go to Admin > CONFIGURE > API Tokens and click Generate Token. Copy the API key.
  2. In Freshchat inside Freshsales Suite, go to Settings > Admin Settings > Website Tracking and APIs > API Settings. Under API DETAILS FOR CHAT, copy Your API Key (click Generate token if it is empty). The same section shows Your chat URL.
  3. Send the key as Authorization: Bearer YOUR_API_KEY.

Your account URL is https://<account-name>.freshchat.com for a standalone account, or your chat URL for Freshsales Suite. API paths go under /v2.

Freshchat has no endpoint that lists every conversation. The raw reports API does: ask for a Chat-Transcript report, wait for it, then download it.

Terminal window
curl -X POST "https://YOUR_ACCOUNT.freshchat.com/v2/reports/raw" \
-H "Authorization: Bearer $FRESHCHAT_API_KEY" \
-H "Content-Type: application/json" \
-d '{"start":"2026-10-25T00:00:00.000Z","end":"2026-10-26T00:00:00.000Z","event":"Chat-Transcript","format":"csv"}'
  • The response holds the report id. Call GET /v2/reports/raw/{id} until status is COMPLETED (or FAILED). Then each entry in links has a download URL in link.href.
  • A Chat-Transcript window can be at most 24 hours, and start can be no earlier than 15 months ago.
  • The download links expire after one hour, so download them as soon as the report is ready.
  • Freshworks’s extract API article says reports can take up to an hour to generate and only one report request is processed per minute. Its raw data article lists 120 Chat-Transcript requests a day.

Rate limits, per Freshchat: the hourly number of API calls depends on your plan. Read it from the X-RateLimit-Limit and X-RateLimit-Remaining response headers.

Each row of the report is one message.

Dimension LabsFreshchatNotes
textmessage_partsA JSON array in the CSV cell. Join the text.content of each part. Skip rows whose message_type is private (private notes) or system.
userIdcustomer_idFreshchat’s id for the customer who started the chat, on every message. Fall back to the sessionId.
incomingactor_typetrue when actor_type is USER. AGENT, BOT and SYSTEM are your side.
sessionIdinteraction_raw_idOne interaction, from the first message until the chat is resolved. A reopened chat gets a new one. Prefix it, for example freshchat-....
conversationIdconversation_idThe whole conversation thread, across interactions.
dimensionlabs_timestampcreated_timeUTC. Convert with toMillis.
platformJsonchannel_name, message_source, actor_typechannel_name is the topic, such as Billing or Sales.

Personal data. Don’t send actor_email, actor_phone, actor_first_name or actor_last_name. Messages can hold names, emails and order numbers: redact them before you send. See PII redaction.

// freshchat.mjs. Environment: FRESHCHAT_URL (for example https://yourcompany.freshchat.com), FRESHCHAT_API_KEY, DL_API_KEY.
// Needs: npm install csv-parse
import { parse } from 'csv-parse/sync';
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const DAY = 24 * HOUR;
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const base = `${process.env.FRESHCHAT_URL.replace(/\/$/, '')}/v2`;
const headers = { Authorization: `Bearer ${process.env.FRESHCHAT_API_KEY}`, 'Content-Type': 'application/json', Accept: 'application/json' };
// Retries rate limits (429) and server errors (5xx), honoring Retry-After.
async function call(url, options = {}) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, options);
if (res.ok) return res;
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Freshchat returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
// Asks for one Chat-Transcript report and waits until it is ready (up to 80 minutes).
async function transcriptReport(start, end) {
const body = JSON.stringify({ start: new Date(start).toISOString(), end: new Date(end).toISOString(), event: 'Chat-Transcript', format: 'csv' });
const { id } = await (await call(`${base}/reports/raw`, { method: 'POST', headers, body })).json();
for (let i = 0; i < 80; i++) {
const report = await (await call(`${base}/reports/raw/${id}`, { headers })).json();
if (report.status === 'COMPLETED') return report;
if (report.status === 'FAILED') throw new Error(`Freshchat report ${id} failed`);
await sleep(60 * 1000);
}
throw new Error(`Freshchat report ${id} was not ready after 80 minutes`);
}
function textOf(row) {
try {
return JSON.parse(row.message_parts || '[]').map((p) => p.text?.content).filter(Boolean).join('\n');
} catch {
return '';
}
}
// Sends every customer, agent and bot message created at or after `since` (Unix ms). Returns the new `since`.
export async function syncFreshchat({ since, historical = false }) {
const now = Date.now();
let start = since;
while (start < now) {
const end = Math.min(start + DAY, now); // Chat-Transcript reports cover 24 hours at most
const report = await transcriptReport(start, end);
for (const { link } of report.links ?? []) {
const csv = await (await call(link.href)).text(); // download right away: links expire after an hour
for (const row of parse(csv, { columns: true, skip_empty_lines: true })) {
const text = textOf(row);
if (row.message_type !== 'normal' || !text.trim()) continue; // skips private notes and system messages
const ts = toMillis(row.created_time);
const sessionId = `freshchat-${row.interaction_raw_id || row.conversation_id}`.replace(/:/g, '-');
await sendToDimensionLabs({
text,
userId: row.customer_id || sessionId,
incoming: String(row.actor_type).toUpperCase() === 'USER',
sessionId,
conversationId: `freshchat-${row.conversation_id}`,
dimensionlabs_timestamp: ts,
platformJson: { channel_name: row.channel_name, message_source: row.message_source, actor_type: row.actor_type },
}, { historical: historical || ts < Date.now() - HOUR });
}
}
start = end;
if (start < now) await sleep(60 * 1000); // Freshworks processes one report request a minute
}
return start;
}

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

  • History first: node run.mjs 2025-08-01 asks for one report per day since that date (15 months back at most) and sends every message to /trackhistorical. Then start a backfill from the Backfill tab.
  • Then every hour: a report can take up to an hour to generate and the daily number of report requests is limited, so run this job hourly rather than every 15 minutes. Use run.mjs on a server or cron, not Lambda: waiting for a report can outlast a Lambda run. Messages that are more than an hour old when the job sees them go to /trackhistorical, and need a backfill.
  • Re-runs are safe: duplicates are removed automatically.

A history load uses one report request per day of history and waits at least a minute between requests, so a long history takes hours, and more than 120 days of history needs more than one day of runs. Run it again with the same date if it stops: what was already sent is deduplicated. You can also upload a file of exported chats with one row per message (check it with the file checker), then run a backfill.

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