# Amazon Connect

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

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](/api/tracker). AWS now calls the product Amazon Connect Customer.

## What you need

- 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](/api/tracker#your-api-key).

## 1. Get access in Amazon Connect

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](https://docs.aws.amazon.com/connect/latest/adminguide/update-instance-settings.html).
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](https://docs.aws.amazon.com/connect/latest/adminguide/enable-analytics.html).
3. In IAM, give the role that runs your sync read access to that bucket.

## 2. Pull new transcripts

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

```text
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`:

```bash
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.

## 3. Map the fields

| Dimension Labs | Amazon Connect | Notes |
|---|---|---|
| `text` | `Transcript[].Content` | For chats, keep items with `Type` `MESSAGE`; skip `EVENT` items. |
| `userId` | contact id | No customer id in the file. Use the contact id, or look up your own internal customer id in your system (never a phone number). |
| `incoming` | `ParticipantRole` | `true` 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. |
| `sessionId` | contact id | From the file name, before `_analysis_`. |
| `conversationId` | contact id | A transfer creates a new contact id. |
| `dimensionlabs_timestamp` | chat: `AbsoluteTime`; call: file time plus `BeginOffsetMillis` | Call 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. |
| `platformJson` | `Channel`, `LanguageCode`, `Sentiment`, `Categories.MatchedCategories` | Sentiment is per turn. Leave out `DisplayName`: it is a participant's name. |

## 4. Send it

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.

```js
// amazon-connect.mjs. Environment: CONNECT_BUCKET, CONNECT_REDACTED (default true), DL_API_KEY.



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;
}
```

## 5. Run it

Save the code as `amazon-connect.mjs` in a project set up as in [Set up your project](/api/tracker#set-up-your-project), and run it with the [scheduled sync](/api/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](/dimensions/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](/data/upload-a-file) built from these JSON files (see [Call transcripts](/transform/recipe-call-transcripts)) and check it first with the [file checker](/data/check-a-file), then run a backfill.

## Amazon Connect reference

- [What is Connect Customer?](https://docs.aws.amazon.com/connect/latest/adminguide/what-is-amazon-connect.html)
- [Update instance settings (Data storage)](https://docs.aws.amazon.com/connect/latest/adminguide/update-instance-settings.html)
- [Enable conversational analytics](https://docs.aws.amazon.com/connect/latest/adminguide/enable-analytics.html)
- [Output file locations for files analyzed by conversational analytics](https://docs.aws.amazon.com/connect/latest/adminguide/example-contact-lens-output-locations.html)
- [Example conversational analytics output files for calls](https://docs.aws.amazon.com/connect/latest/adminguide/contact-lens-example-output-files.html)
- [Example conversational analytics output files for chats](https://docs.aws.amazon.com/connect/latest/adminguide/contact-lens-example-output-files-chat.html)
- [Amazon S3 ListObjectsV2](https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html)
- [Amazon S3 GetObject](https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html)

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