# Google Dialogflow CX

> Send agent conversations from Google Dialogflow CX to Dimension Labs with the universal API.

This brings in your Dialogflow CX (Conversational Agents) conversations, turn by turn: what each end user said or typed and what the agent answered. Each Dialogflow conversation becomes one conversation. You run it on your side: a small script pulls from the Dialogflow CX Conversations API and posts each message to the [universal API](/api/tracker).

The Conversations API returns each turn's user text and agent text as plain strings, which keeps the code short. If you already export conversation history to BigQuery, you can use that table instead: see [BigQuery export](#bigquery-export-instead).

## What you need

- **Conversation history** turned on for the agent. Dialogflow only stores conversations from the time it is on.
- A Google Cloud service account with the IAM permissions `dialogflow.conversations.list` and `dialogflow.conversations.get` on the agent's project. A custom role with just those two is enough.
- Your project id, the agent's location (for example `global` or `us-central1`) and the agent id.
- A Dimension Labs API key for a new **Universal API** source. See [Your API key](/api/tracker#your-api-key).

## 1. Get API access in Dialogflow CX

1. Turn on conversation history: open the Conversational Agents console, choose your project and agent, and click the settings icon in the top right corner. Under **Logging settings**, turn on **Enable conversation history**, then click **Save** ([agent settings](https://docs.cloud.google.com/dialogflow/cx/docs/concept/agent-settings)).
2. In the Google Cloud console, create a service account for the job, and grant it a role with `dialogflow.conversations.list` and `dialogflow.conversations.get` ([access control](https://docs.cloud.google.com/dialogflow/cx/docs/concept/access-control)).
3. On the **Service accounts** page, click the account's email address, open the **Keys** tab, and choose **Add key > Create new key**. Select **JSON** and click **Create** ([create a service account key](https://docs.cloud.google.com/iam/docs/keys-create-delete)). Store the file in a secrets manager. The code finds it through the `GOOGLE_APPLICATION_CREDENTIALS` environment variable. On Google Cloud, an attached service account works without a key file ([Application Default Credentials](https://docs.cloud.google.com/docs/authentication/application-default-credentials)).

Requests need the OAuth scope `https://www.googleapis.com/auth/dialogflow` (or `cloud-platform`). The [google-auth-library](https://www.npmjs.com/package/google-auth-library) package gets the token for you: `npm install google-auth-library`.

**Use the right host.** For the `global` location the host is `dialogflow.googleapis.com`; for any other region it is `<region>-dialogflow.googleapis.com`, and the region must also appear in the path ([regionalization](https://docs.cloud.google.com/dialogflow/cx/docs/concept/region)).

## 2. Pull new conversations

[List conversations](https://docs.cloud.google.com/dialogflow/cx/docs/reference/rest/v3beta1/projects.locations.agents.conversations/list) created since a point in time:

```bash
curl -H "Authorization: Bearer $(gcloud auth print-access-token)" \
  "https://dialogflow.googleapis.com/v3beta1/projects/YOUR_PROJECT/locations/global/agents/YOUR_AGENT_ID/conversations?pageSize=1000&filter=createTime%20%3E%20%222026-10-01T00:00:00Z%22"
```

- `filter` takes `createTime > "<RFC 3339 time>"`. Results come newest first.
- `pageSize` defaults to 100 and goes up to 1,000. Pass `nextPageToken` as `pageToken` until it is empty.
- The list leaves `interactions` empty. Call `GET https://<host>/v3beta1/<conversation name>` for each conversation to get its turns.
- Each interaction has `requestUtterances` (the user's text, or the transcript of their audio), `responseUtterances` (the agent's text or the transcript of its audio, joined into one string) and `createTime`.
- Dialogflow logs at most 500 turns per conversation. How long it keeps conversations is set by the `retention_window_days` security setting.
- The Conversations API is on the `v3beta1` version of the API.

A Dialogflow session stays open for 30 minutes after the last request by default, so a conversation can keep growing after it first appears. The code re-reads the conversations created in the last two hours on every run.

## 3. Map the fields

| Dimension Labs | Dialogflow CX | Notes |
|---|---|---|
| `text` | interaction `requestUtterances` (user), `responseUtterances` (agent) | One interaction gives up to two messages. Skip empty ones, such as an event-only request. |
| `userId` | conversation id | Dialogflow doesn't give an end-user id here, so use the `sessionId` on both sides. |
| `incoming` | which field the text came from | `true` for `requestUtterances`, `false` for `responseUtterances`. |
| `sessionId` | the last segment of the conversation `name` | Prefix it, for example `dialogflow-<conversation id>`. |
| `conversationId` | the last segment of the conversation `name` | Same value as `sessionId`. |
| `dimensionlabs_timestamp` | interaction `createTime` | Convert with `toMillis`. The agent's reply gets the same time plus 1,000 ms. |
| `platformJson` | conversation `languageCode`, `type`, `environment.displayName`, `metrics.hasLiveAgentHandoff`; interaction `response.queryResult.match.intent.displayName`, `response.queryResult.currentPage.displayName` | `type` is `TEXT`, `AUDIO` or `UNDETERMINED`. |

**Personal data.** End users sometimes say or type names, emails or account numbers: redact them before you send. See [PII redaction](/data/pii-redaction).

## 4. Send it

```js
// dialogflow-cx.mjs. Environment: GOOGLE_APPLICATION_CREDENTIALS (service account key file), DIALOGFLOW_PROJECT, DIALOGFLOW_LOCATION (for example global), DIALOGFLOW_AGENT_ID, DL_API_KEY.



const HOUR = 60 * 60 * 1000;
const LOOKBACK = 2 * HOUR; // re-read recent conversations: they keep growing until the session ends
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const auth = new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/dialogflow' });
const LOC = process.env.DIALOGFLOW_LOCATION;
const HOST = `https://${LOC === 'global' ? '' : `${LOC}-`}dialogflow.googleapis.com/v3beta1`;
const AGENT = `projects/${process.env.DIALOGFLOW_PROJECT}/locations/${LOC}/agents/${process.env.DIALOGFLOW_AGENT_ID}`;

// Retries Dialogflow's rate limits (429) and server errors (5xx), honoring Retry-After.
async function get(url) {
  for (let attempt = 0; ; attempt++) {
    const res = await fetch(url, { headers: { Authorization: `Bearer ${await auth.getAccessToken()}` } });
    if (res.ok) return res.json();
    if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Dialogflow returned ${res.status}: ${await res.text()}`);
    await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
  }
}

// Sends every turn of the conversations created since `since` (Unix ms), minus the lookback. Returns the new `since`.
export async function syncDialogflowCx({ since, historical = false }) {
  const started = Date.now();
  const filter = `createTime > "${new Date(since - LOOKBACK).toISOString()}"`;
  const names = [];
  let pageToken = '';
  do {
    const page = await get(`${HOST}/${AGENT}/conversations?pageSize=1000&filter=${encodeURIComponent(filter)}${pageToken ? `&pageToken=${encodeURIComponent(pageToken)}` : ''}`);
    names.push(...(page.conversations ?? []).map((c) => c.name));
    pageToken = page.nextPageToken;
  } while (pageToken);

  for (const name of names.reverse()) { // oldest first
    const c = await get(`${HOST}/${name}`);
    const id = `dialogflow-${name.split('/').pop()}`.replace(/:/g, '-');
    const turns = (c.interactions ?? []).slice().sort((a, b) => toMillis(a.createTime) - toMillis(b.createTime));
    for (const i of turns) {
      const ts = toMillis(i.createTime);
      const result = i.response?.queryResult;
      const platformJson = {
        language: c.languageCode, type: c.type, environment: c.environment?.displayName,
        live_agent_handoff: c.metrics?.hasLiveAgentHandoff,
        intent: result?.match?.intent?.displayName, page: result?.currentPage?.displayName,
      };
      for (const [text, incoming, at] of [[i.requestUtterances, true, ts], [i.responseUtterances, false, ts + 1000]]) {
        if (!text?.trim()) continue;
        await sendToDimensionLabs({
          text,
          userId: id,
          incoming,
          sessionId: id,
          conversationId: id,
          dimensionlabs_timestamp: at,
          platformJson,
        }, { historical: historical || at < Date.now() - HOUR });
      }
    }
  }
  return started;
}
```

## 5. Run it

Save the code as `dialogflow-cx.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 { syncDialogflowCx as sync } from './dialogflow-cx.mjs';`.

- **History first:** `node run.mjs 2026-01-01` sends every conversation created since that date to `/trackhistorical`, as far back as your retention setting allows. Then start a [backfill](/dimensions/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 run re-sends the turns of the last two hours of conversations, so late turns are caught; the repeats are dropped. If your sessions run longer than that (`sessionTtl` can be up to a day), raise `LOOKBACK`.

Each conversation costs one `get` call, so a long history takes a while; the code waits whenever Google asks it to. In the console, **Conversation history > Export all conversations** downloads at most 50 conversations as CSV, so for a file route use BigQuery.

### BigQuery export instead

Dialogflow can write all live conversation history to a BigQuery table, one row per turn, with `conversation_name`, `turn_position`, `request_time`, and the `request` and `response` as JSON ([export to BigQuery](https://docs.cloud.google.com/dialogflow/cx/docs/concept/export-bq)). You create the dataset and table yourself, then turn on BigQuery export in the agent settings. It needs conversation history turned on too, and only covers conversations from the time it is set up.

If you use it, read new rows by `request_time` and follow the [BigQuery guide](/integrations/bigquery), with the same direction rules: the user's input from `request` is incoming, the agent's reply from `response` is outgoing, and `conversation_name` is the session.

## Dialogflow CX reference

- [Conversation history](https://docs.cloud.google.com/dialogflow/cx/docs/concept/conversation-history)
- [Agent settings](https://docs.cloud.google.com/dialogflow/cx/docs/concept/agent-settings)
- [Access control](https://docs.cloud.google.com/dialogflow/cx/docs/concept/access-control)
- [Regionalization](https://docs.cloud.google.com/dialogflow/cx/docs/concept/region)
- [Create and delete service account keys](https://docs.cloud.google.com/iam/docs/keys-create-delete)
- [Application Default Credentials](https://docs.cloud.google.com/docs/authentication/application-default-credentials)
- [conversations.list](https://docs.cloud.google.com/dialogflow/cx/docs/reference/rest/v3beta1/projects.locations.agents.conversations/list)
- [Conversation resource](https://docs.cloud.google.com/dialogflow/cx/docs/reference/rest/v3beta1/projects.locations.agents.conversations)
- [Conversation history export to BigQuery](https://docs.cloud.google.com/dialogflow/cx/docs/concept/export-bq)

Checked against Google's documentation on 2026-10-26.
