Skip to content

02 Get your data in /Integrations

Twilio Conversations and Flex

Send messages from Twilio Conversations, including Flex messaging, to Dimension Labs with the universal API.

  1. TC TwilioTickets, 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 the messages of your Twilio Conversations: SMS, WhatsApp, chat and the other channels it carries. Flex messaging channels run on Twilio Conversations, so the same steps cover Flex chats. You run it on your side with the universal API. In Twilio’s docs the REST resources are listed under Twilio Conversations (classic).

  • A Twilio account that can create API keys.
  • A Twilio API key (SID and secret). A Standard key works. A Restricted key needs read access to Conversations.
  • The identities your agents and bots use in Conversations, so you can tell their messages from the customer’s.
  • A Dimension Labs API key for a new Universal API source. See Your API key.

Twilio’s own steps are in Create API keys in Twilio Console.

  1. In Twilio Console, go to Settings > Account settings > API keys & auth tokens.
  2. Select your region, then click Create API key.
  3. Enter a name and choose Standard (or Restricted, and select its permissions). Click Next.
  4. Copy the secret and store it in a secrets manager, select Got it! and click Finish.

Twilio uses HTTP Basic authentication: the API key SID is the username and the secret is the password.

List conversations, then the messages of each one.

Terminal window
curl -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
"https://conversations.twilio.com/v1/Conversations?StartDate=2026-10-12T00:00:00Z&PageSize=100"
curl -u "$TWILIO_API_KEY_SID:$TWILIO_API_KEY_SECRET" \
"https://conversations.twilio.com/v1/Conversations/CHXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/Messages?Order=asc&PageSize=100"
  • StartDate filters by the conversation’s creation date and must start at the beginning of a day (YYYY-MM-DDT00:00:00Z). There is no “updated since” filter, so look back a few days and only send messages newer than your cursor.
  • State filters by active, inactive or closed.
  • PageSize goes up to 100 (default 50). Each response has meta.next_page_url. Request it until it is null.
  • Messages default to Order=asc (oldest first).
  • Twilio limits state-changing actions (default 30 per second). Twilio’s limits page says read actions are not limited under that rule.
Dimension LabsTwilioNotes
textbodyUp to 1,600 characters. Skip messages with no body (media only).
userIdconversation sidOn every message. Don’t use the customer’s author: it is their phone number, WhatsApp address or chat identity. To link one customer’s conversations, look up their participant in GET /v1/Conversations/{sid}/Participants and send a hash of the address instead.
incomingauthorTwilio has no field that marks the customer. author is the participant’s channel identifier and defaults to system. Treat a message as incoming: false when author is system or one of your agent or bot identities.
sessionIdconversation sidStarts with CH.
conversationIdconversation sid
dimensionlabs_timestampdate_createdConvert with toMillis.
platformJsonindex, participant_sid, conversation friendly_name, state, attributesattributes is a JSON string you set; parse it for channel or account ids.

Personal data. Never send author for customer messages. Message text can hold names, addresses and card details: redact it before you send. See PII redaction.

// twilio.mjs. Environment: TWILIO_API_KEY_SID, TWILIO_API_KEY_SECRET, TWILIO_AGENT_IDENTITIES
// (a comma-separated list of the identities your agents and bots use), DL_API_KEY.
import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';
const HOUR = 60 * 60 * 1000;
const LOOKBACK_DAYS = 3; // raise it if your conversations stay open longer
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
const auth = 'Basic ' + Buffer.from(`${process.env.TWILIO_API_KEY_SID}:${process.env.TWILIO_API_KEY_SECRET}`).toString('base64');
const AGENTS = new Set((process.env.TWILIO_AGENT_IDENTITIES ?? '').split(',').filter(Boolean));
// Retries Twilio's rate limits (429) and server errors (5xx) with a growing pause.
async function get(url) {
for (let attempt = 0; ; attempt++) {
const res = await fetch(url, { headers: { Authorization: auth } });
if (res.ok) return res.json();
if ((res.status !== 429 && res.status < 500) || attempt === 5) throw new Error(`Twilio returned ${res.status}: ${await res.text()}`);
await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000);
}
}
async function* pages(url, key) {
for (let next = url; next; ) { const p = await get(next); yield* p[key]; next = p.meta.next_page_url; }
}
// Sends every message created at or after `since` (Unix ms). Returns the new `since`.
export async function syncTwilio({ since, historical = false }) {
const day = new Date(since - LOOKBACK_DAYS * 24 * HOUR).toISOString().slice(0, 10);
let newest = since;
const convos = `https://conversations.twilio.com/v1/Conversations?StartDate=${day}T00:00:00Z&PageSize=100`;
for await (const c of pages(convos, 'conversations')) {
const msgs = `https://conversations.twilio.com/v1/Conversations/${c.sid}/Messages?Order=asc&PageSize=100`;
for await (const m of pages(msgs, 'messages')) {
const ts = toMillis(m.date_created);
if (ts < since || !m.body?.trim()) continue;
const fromUs = m.author === 'system' || AGENTS.has(m.author);
await sendToDimensionLabs({
text: m.body,
userId: c.sid,
incoming: !fromUs,
sessionId: c.sid,
conversationId: c.sid,
dimensionlabs_timestamp: ts,
platformJson: { index: m.index, participant_sid: m.participant_sid, state: c.state, friendly_name: c.friendly_name },
}, { historical: historical || ts < Date.now() - HOUR });
newest = Math.max(newest, ts);
}
}
return newest;
}

StartDate filters by when a conversation was created, so each run also looks back LOOKBACK_DAYS days for conversations that are still getting messages. If you set friendly_name to something that names the customer, leave it out of platformJson.

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

  • History first: node run.mjs 2025-01-01 sends every message since that date to /trackhistorical. Then start a 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.

You can also upload a file and check it first with the file checker, then run a backfill.

Checked against Twilio’s documentation on 2026-10-13.