Skip to content

02 Get your data in /Integrations

Amazon Connect

Send chat transcripts and call transcripts from Amazon Connect to Dimension Labs with the universal API.

  1. AC Amazon ConnectTickets, 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 Amazon Connect chat transcripts and call transcripts, turn by turn, from the files that conversational analytics (Contact Lens) writes to your instance’s S3 bucket. You run it on your side with the universal API. AWS now calls the product Amazon Connect Customer.

  • An Amazon Connect instance with conversational analytics turned on for the flows you want: speech analytics for calls, chat analytics for chats.
  • An IAM role or user for your sync with s3:ListBucket and s3:GetObject on the instance’s bucket. If the bucket is encrypted with a KMS key, the role also needs to use that key.
  • A Dimension Labs API key for a new Universal API source. See Your API key.
  1. In the Amazon Connect console, open your instance and choose Data storage. Note the S3 bucket set there. AWS’s steps are in Update instance settings.
  2. In your flows, add a Set recording and analytics behavior block (or Set recording, analytics and processing behavior in new flows) and turn on conversational analytics: Enable chat analytics for chats, and call recording plus speech analytics for calls. AWS’s steps are in Enable conversational analytics.
  3. In IAM, give the role that runs your sync read access to that bucket.

Conversational analytics writes one JSON file per contact, in folders by date:

Analysis/Voice/2026/10/13/{contact id}_analysis_2026-10-13T21:14:16Z.json
Analysis/Chat/2026/10/13/{contact id}_analysis_2026-10-13T21:14:16Z.json

When you turn on redaction in the flow block, redacted copies sit under Analysis/Voice/Redacted/ and Analysis/Chat/Redacted/. The code below reads those by default, because Dimension Labs doesn’t store personal data. Set CONNECT_REDACTED=false only if your files hold no personal data.

List new files with S3 ListObjectsV2:

Terminal window
aws s3api list-objects-v2 --bucket YOUR_CONNECT_BUCKET --prefix "Analysis/Chat/2026/10/13/"
  • ListObjectsV2 returns up to 1,000 keys per request. When IsTruncated is true, send NextContinuationToken as ContinuationToken for the next page.
  • Keys inside a day are not in time order, so use each file’s LastModified as the cursor: send files modified after the last run and up to the start of this one.
  • The date in the path is when the agent connected (ConnectedToAgentTimestamp), not when the contact started.
Dimension LabsAmazon ConnectNotes
textTranscript[].ContentFor chats, keep items with Type MESSAGE; skip EVENT items.
userIdcontact idNo customer id in the file. Use the contact id, or look up your own internal customer id in your system (never a phone number).
incomingParticipantRoletrue when the role is CUSTOMER. AGENT and SYSTEM are your side. Call turns carry a ParticipantId; look its role up in the file’s Participants list.
sessionIdcontact idFrom the file name, before _analysis_.
conversationIdcontact idA transfer creates a new contact id.
dimensionlabs_timestampchat: AbsoluteTime; call: file time plus BeginOffsetMillisCall turns only carry offsets. Add each turn’s BeginOffsetMillis to the time in the file name, which is when the agent connected. Turns closer than a second apart are spaced to 1,000 ms.
platformJsonChannel, LanguageCode, Sentiment, Categories.MatchedCategoriesSentiment is per turn. Leave out DisplayName: it is a participant’s name.

Install the S3 client in your project first: npm install @aws-sdk/client-s3. The role the job runs as needs s3:ListBucket and s3:GetObject on the bucket.

// amazon-connect.mjs. Environment: CONNECT_BUCKET, CONNECT_REDACTED (default true), DL_API_KEY.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
import { S3Client, ListObjectsV2Command, GetObjectCommand } from '@aws-sdk/client-s3';
const HOUR = 60 * 60 * 1000;
const DAY = 24 * HOUR;
const s3 = new S3Client({}); // the SDK retries throttling and server errors itself
const Bucket = process.env.CONNECT_BUCKET;
const REDACTED = process.env.CONNECT_REDACTED !== 'false' ? 'Redacted/' : '';
// Sends every transcript file written at or after `since` (Unix ms). Returns the new `since`.
export async function syncConnect({ since, historical = false }) {
let newest = since;
for (const kind of ['Voice', 'Chat']) {
// Folders are dated by when the agent connected, so start a day early for contacts that crossed midnight.
for (let d = since - DAY; d <= Date.now(); d += DAY) {
const day = new Date(d).toISOString().slice(0, 10).replace(/-/g, '/');
let token;
do {
const list = await s3.send(new ListObjectsV2Command({ Bucket, Prefix: `Analysis/${kind}/${REDACTED}${day}/`, ContinuationToken: token }));
for (const obj of list.Contents ?? []) {
const modified = obj.LastModified.getTime();
const name = obj.Key.split('/').pop().match(/^(.+?)_analysis_(?:redacted_)?(.+)\.json$/i);
if (modified < since || !name) continue;
const [, contactId, connected] = name;
const file = JSON.parse(await (await s3.send(new GetObjectCommand({ Bucket, Key: obj.Key }))).Body.transformToString());
const roles = Object.fromEntries((file.Participants ?? []).map((p) => [p.ParticipantId, p.ParticipantRole]));
let last = 0;
for (const t of file.Transcript ?? []) {
if (kind === 'Chat' && t.Type !== 'MESSAGE') continue;
if (!t.Content?.trim()) continue;
const role = t.ParticipantRole ?? roles[t.ParticipantId];
const at = kind === 'Chat' ? toMillis(t.AbsoluteTime) : toMillis(connected) + t.BeginOffsetMillis;
const ts = Math.max(at, last + 1000); // keep turns at least a second apart
last = ts;
await sendToDimensionLabs({
text: t.Content,
userId: contactId,
incoming: role === 'CUSTOMER',
sessionId: contactId,
conversationId: contactId,
dimensionlabs_timestamp: ts,
platformJson: { channel: file.Channel, language: file.LanguageCode, sentiment: t.Sentiment, role, categories: file.Categories?.MatchedCategories },
}, { historical: historical || ts < Date.now() - HOUR });
}
newest = Math.max(newest, modified);
}
token = list.IsTruncated ? list.NextContinuationToken : undefined;
} while (token);
}
}
return newest;
}

Save the code as amazon-connect.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncConnect as sync } from './amazon-connect.mjs';. On Lambda, add the S3 permissions to the function’s execution role.

  • History first: node run.mjs 2025-01-01 sends every file since that date to /trackhistorical. Then start a backfill from the Backfill tab.
  • Then every 15 minutes: the scheduled job sends new transcripts to /track, and they are enriched automatically every day. Contact Lens writes a file after the contact ends, so the turns of a contact that lasted 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 built from these JSON files (see Call transcripts) and check it first with the file checker, then run a backfill.

Checked against Amazon Connect’s documentation on 2026-10-13.