Skip to content

02 Get your data in /Integrations

Vonage Contact Center

Send call transcripts from Vonage Contact Center to Dimension Labs with the universal API.

  1. VC Vonage Contact CenterTickets, 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 Vonage Contact Center call transcripts from Conversation Analyzer, one call as one message. One call becomes one conversation in Dimension Labs. You run it on your side: a small script pulls from the Vonage Contact Center (VCC) APIs and posts each transcript to the universal API.

The transcript the API returns is one block of text with no speakers, so each call is sent as a single message, as described in Transcripts in one block. Dimensions are still extracted from the whole call. What you can’t do is separate what the customer said from what your agent said.

  • Conversation Analyzer on your VCC account. It is a provisioned feature: if you don’t have it, ask Vonage. Call recording must be on too.
  • Calls set up for analysis. Vonage lists these settings:
    • In User Admin > Agent Settings, set Transcribe calls to Yes for each agent whose calls you want.
    • Your interaction plan sets a language and a Conversation Analyzer profile on each call. Without both, Conversation Analyzer doesn’t analyze the call.
  • API credentials with the interaction-content:read scope, created by a user who can see the calls: View inbound interaction content, View outbound interaction content, and access to the groups of the agents who took the calls. A token has the permissions of the user who created the credentials.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

1. Get API access in Vonage Contact Center

Section titled “1. Get API access in Vonage Contact Center”

Vonage’s steps are in How to authenticate with a Vonage Contact Center (VCC) API.

  1. Log in to the VCC Admin Portal and go to API Credentials. If you don’t see it, contact VCC support.
  2. Click Create New. Give the credentials a name, and under Scopes add interaction-content:read.
  3. Click Save API credential, and copy the client ID and secret. You can’t see the secret again.
  4. Find your region: emea, nam (USA) or apac. The code takes it as VONAGE_REGION.
  5. Get a token: POST https://YOUR_REGION.cc.vonage.com/Auth/connect/token with a form body of grant_type=client_credentials, client_id, client_secret and scope=interaction-content:read. Send it as Authorization: Bearer YOUR_TOKEN. Vonage says a token lasts 15 minutes by default, so the code gets a new one when a request returns 401.

Every request also needs a version in the Accept header. The code uses application/vnd.newvoicemedia.v3+json.

Find calls that have a transcript with the Interaction Content API, then get their transcripts from the Conversation Analyzer API, up to 50 calls at a time:

Terminal window
curl -H "Authorization: Bearer $VCC_TOKEN" -H "Accept: application/vnd.newvoicemedia.v3+json" \
"https://nam.api.cc.vonage.com/interaction-content/interactions?start=2026-10-20T00:00:00Z&end=2026-10-26T00:00:00Z"
curl -H "Authorization: Bearer $VCC_TOKEN" -H "Accept: application/vnd.newvoicemedia.v3+json" \
"https://nam.api.cc.vonage.com/conversation-analyzer/interactions?calls=GUID_1,GUID_2&transcript=true"
  • start and end are ISO 8601, and a search can span at most 7 days. The code searches 6 days at a time, from VONAGE_LOOKBACK_DAYS before the cursor, and keeps calls whose transcript changed since the last run.
  • The search only returns interactions that have at least one content file. Each item has guid, start, direction, handlingAgents and content[]. A call with a transcript has a content item with type transcript, and its lastModified is the code’s cursor.
  • limit sets the page size (1 to 1,000, 1,000 by default). When meta.nextContinuationToken is set, send it as continuationToken for the next page.
  • calls takes up to 50 comma-separated GUIDs. With transcript=true, each item has transcript, a single string of the call’s text with no speaker labels. It also has status (processing, processed or failed), language, confidence, categories, callTimeBreakDown (seconds of agent talk, customer talk, silence and cross talk) and, when sentiment is on, sentimentSummary.

About speakers. Conversation Analyzer separates the speakers on its own screen, but the API’s transcript doesn’t: Vonage’s example is "Case.\nHi, and yeah, just a call here. Thank you bye.". The Interaction Content API can also return the transcript as a JSON file (GET /interaction-content/interactions/{guid}/content/transcript), but Vonage doesn’t document that file’s layout. If you download one and it does label each line with a speaker, you can split it into turns instead: see Transcripts in one block.

Dimension LabsVonage Contact CenterNotes
texttranscriptThe whole call, with line breaks turned into spaces. Calls with an empty transcript are skipped.
userIdguidThe API has no customer id: connectFrom and connectTo are phone numbers. The code uses the sessionId, or look up your own internal customer id in your system.
incomingnoneAlways true: the transcript holds the customer’s words, and there is nothing to split it on.
sessionIdguidOne call is one conversation in Dimension Labs.
conversationIdguidSame value as sessionId.
dimensionlabs_timestampstartWhen the call started.
platformJsondirection, language, confidence, callTimeBreakDown, category names, sentimentSummary.overallSentimentMarked transcript: 'unsplit' so you can filter these records. Leave out connectFrom and connectTo: they are phone numbers.

Personal data. Call transcripts hold names, phone numbers, card and account numbers, and addresses. Vonage has a personal data redaction feature for call recordings and transcripts (a provisioned feature, for English language profiles); use it, or redact before you send. See PII redaction.

// vonage-contact-center.mjs. Environment: VONAGE_REGION (emea, nam or apac), VONAGE_CLIENT_ID, VONAGE_CLIENT_SECRET,
// VONAGE_LOOKBACK_DAYS (default 2), DL_API_KEY.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const DAY = 24 * HOUR;
const WINDOW = 6 * DAY; // a search can span at most 7 days
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const REGION = process.env.VONAGE_REGION ?? 'nam';
const API = `https://${REGION}.api.cc.vonage.com`;
const LOOKBACK = Number(process.env.VONAGE_LOOKBACK_DAYS ?? 2) * DAY;
let token;
async function newToken() {
const body = new URLSearchParams({
grant_type: 'client_credentials', client_id: process.env.VONAGE_CLIENT_ID,
client_secret: process.env.VONAGE_CLIENT_SECRET, scope: 'interaction-content:read',
});
const res = await fetch(`https://${REGION}.cc.vonage.com/Auth/connect/token`, { method: 'POST', body });
if (!res.ok) throw new Error(`Vonage token request returned ${res.status}: ${await res.text()}`);
token = (await res.json()).access_token;
}
// Gets a fresh token on 401, and retries rate limits (429) and server errors (5xx). Returns null on 404.
async function get(path) {
for (let attempt = 0; ; attempt++) {
if (!token) await newToken();
const res = await fetch(`${API}${path}`, {
headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.newvoicemedia.v3+json' },
});
if (res.ok) return res.json();
if (res.status === 404) return null;
if (res.status === 401 && attempt === 0) { token = null; continue; }
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Vonage returned ${res.status} for ${path}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
// Calls whose transcript changed between `since` and `until`, with that time as `changed`.
async function* callsWithNewTranscripts(since, until) {
for (let from = since - LOOKBACK; from < until; from += WINDOW) {
const to = Math.min(from + WINDOW, until);
let continuationToken;
do {
const q = new URLSearchParams({ start: new Date(from).toISOString(), end: new Date(to).toISOString(), limit: '1000' });
if (continuationToken) q.set('continuationToken', continuationToken);
const page = await get(`/interaction-content/interactions?${q}`);
for (const item of page?.items ?? []) {
const t = (item.content ?? []).find((c) => c.type === 'transcript');
const changed = t?.lastModified ? toMillis(t.lastModified) : null;
if (changed !== null && changed >= since && changed < until) yield { ...item, changed };
}
continuationToken = page?.meta?.nextContinuationToken;
} while (continuationToken);
}
}
async function sendBatch(batch, historical) {
let held = Infinity; // the earliest call still being processed: the cursor stays before it
const found = await get(`/conversation-analyzer/interactions?calls=${batch.map((c) => c.guid).join(',')}&transcript=true`);
const byGuid = new Map((Array.isArray(found) ? found : []).map((r) => [r.guid, r]));
for (const call of batch) {
const r = byGuid.get(call.guid);
if (r?.status === 'processing') { held = Math.min(held, call.changed); continue; }
const text = (r?.transcript ?? '').replace(/\s+/g, ' ').trim();
if (r?.status !== 'processed' || !text) continue;
const sessionId = `vonage-${call.guid}`.replace(/:/g, '-');
const ts = toMillis(call.start);
await sendToDimensionLabs({
text,
userId: sessionId, // no customer id in the API; connectFrom is a phone number
incoming: true, // it holds the customer's words
sessionId,
conversationId: sessionId,
dimensionlabs_timestamp: ts,
platformJson: {
transcript: 'unsplit', channel: 'voice', direction: call.direction, language: r.language, confidence: r.confidence,
callTimeBreakDown: r.callTimeBreakDown ?? r.callTimeBreakdown,
categories: (r.categories ?? []).map((c) => c.category),
sentiment: r.sentimentSummary?.overallSentiment,
},
}, { historical: historical || ts < Date.now() - HOUR });
}
return held;
}
// Sends every call transcript that appeared or changed at or after `since` (Unix ms). Returns the new `since`.
export async function syncVonage({ since, historical = false }) {
token = null; // a new token for each run
const until = Date.now();
let newest = since;
let held = Infinity;
let batch = [];
for await (const call of callsWithNewTranscripts(since, until)) {
batch.push(call);
newest = Math.max(newest, call.changed);
if (batch.length === 50) { held = Math.min(held, await sendBatch(batch, historical)); batch = []; }
}
if (batch.length) held = Math.min(held, await sendBatch(batch, historical));
return Math.min(newest, held);
}

A call that Conversation Analyzer is still processing keeps the cursor where it is, so the next run asks for it again. Calls already sent in between come back too, which is fine: duplicates are removed automatically.

This guide covers calls. Chat transcripts are content files too (type chatTranscript, fetched by their contentKey), but Vonage doesn’t document their layout. If you want them, download one and check it against Transcripts in one block.

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

  • History first: node run.mjs 2025-01-01 sends every call transcript since that date to /trackhistorical. Then start a backfill from the Backfill tab. Only calls whose content your retention settings still keep have a transcript.
  • Then every 15 minutes: the scheduled job sends new transcripts to /track, and they are enriched automatically every day. Conversation Analyzer processes a call after it ends, so a transcript can arrive more than an hour after the call started: those go to /trackhistorical, and need a backfill. The search looks back VONAGE_LOOKBACK_DAYS (2 by default) for calls whose transcript is new; raise it if your transcripts take longer.
  • Re-runs are safe: duplicates are removed automatically.
  • Rate limits: the code waits whenever Vonage returns 429.

Vonage also sends an interaction.transcriptavailable.v1 webhook event when a transcript is ready, if you’d rather react to events than poll. You can also upload a file and check it first with the file checker, then run a backfill.

Checked against Vonage Contact Center’s documentation on 2026-10-26.