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.
- TC TwilioTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- 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).
What you need
Section titled “What you need”- 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.
1. Get API access in Twilio
Section titled “1. Get API access in Twilio”Twilio’s own steps are in Create API keys in Twilio Console.
- In Twilio Console, go to Settings > Account settings > API keys & auth tokens.
- Select your region, then click Create API key.
- Enter a name and choose Standard (or Restricted, and select its permissions). Click Next.
- 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.
2. Pull new messages
Section titled “2. Pull new messages”List conversations, then the messages of each one.
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"StartDatefilters 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.Statefilters byactive,inactiveorclosed.PageSizegoes up to 100 (default 50). Each response hasmeta.next_page_url. Request it until it isnull.- 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.
3. Map the fields
Section titled “3. Map the fields”| Dimension Labs | Twilio | Notes |
|---|---|---|
text | body | Up to 1,600 characters. Skip messages with no body (media only). |
userId | conversation sid | On 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. |
incoming | author | Twilio 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. |
sessionId | conversation sid | Starts with CH. |
conversationId | conversation sid | |
dimensionlabs_timestamp | date_created | Convert with toMillis. |
platformJson | index, participant_sid, conversation friendly_name, state, attributes | attributes 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.
4. Send it
Section titled “4. Send it”// 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 longerconst 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.
5. Run it
Section titled “5. Run it”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-01sends 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.
Twilio reference
Section titled “Twilio reference”- Create API keys in Twilio Console
- API keys overview
- Twilio API requests
- Conversation resource
- Conversation Message resource
- Conversation Participant resource
- Conversations (classic) limits
- Flex Conversations
Checked against Twilio’s documentation on 2026-10-13.