How to Sync Customer Audiences from Any CDP with a Unified API
August 4, 2026
Your product can read a customer's audiences from whichever customer data platform they run, through one connection and one method call. sdk.cdp.listCdpSegments returns the same object whether the customer is on Twilio Segment, Adobe Experience Platform, Lytics, or Tealium, and a webhook subscription keeps your copy of that list current as the customer builds and edits audiences in their own tool. Every request is executed directly against the customer's CDP, so what you read is what exists there now.
Why audiences are the hard part of a CDP integration
Any product that targets, messages, personalizes, or reports on groups of people needs to know which group the customer means. The customer has already answered that question, in detail, inside their CDP. Reading their answer is the integration.
The difficulty is that each platform models the same idea differently and names it differently.
| Concept | Twilio Segment | mParticle | Adobe Experience Platform | Unified object |
|---|---|---|---|---|
| Where data arrives | Source | Input | Source | Source |
| Where data leaves | Destination | Output | Destination | Destination |
| A defined group of people | Audience (Engage) | Audience | Audience / segment definition | Segment |
| Sending a group somewhere | Connecting an audience to a destination | Connection | Activation via dataflow | Activation |
Building against each one means a separate integration, separate auth, and separate handling for pagination, rate limits, and errors, before you have written any of your own product. The Unified CDP API normalizes the concept into a single Segment object so you build the read once. |
What you are building
An audience list inside your product: the customer connects their CDP, sees their own audiences, and picks the ones your feature should act on. The list stays current without a nightly job.
- Authorize the customer's CDP and store one connection ID
- List their audiences
- Show the detail their platform provides
- Subscribe to changes so the list updates itself
Connect once and list every audience
Initialize the SDK with your API key. This is the same for every category.
import { UnifiedTo } from '@unified-api/typescript-sdk';
import * as errors from '@unified-api/typescript-sdk/sdk/models/errors';
const sdk = new UnifiedTo({
security: {
jwt: process.env.UNIFIED_API_KEY,
},
});
If your customers require their data to stay in a region, pass serverIdx when initializing: 0 for the North American server, 1 for European, 2 for Australian.
Each end-customer authorizes their own CDP through Unified's authorization component, which returns a connection ID. Store one per customer. That ID is the only thing that changes between a customer on Twilio Segment and a customer on Adobe Experience Platform.
Listing their audiences takes one call:
const segments = await sdk.cdp.listCdpSegments({
connectionId,
limit: 50,
offset: 0,
sort: 'updated_at',
order: 'desc',
});
id and name are your floor. Every integration that supports the Segment object returns them, which is enough to render a working picker on day one.
What each field tells you, and why it changes your interface
The rest of the Segment object is where a generic list becomes something a customer trusts. Five fields carry information most audience pickers do not show, because most audience pickers are built against one platform and inherit its assumptions.
compute_mode returns REALTIME or BATCH. This is the field to read first, because it bounds what your product can honestly promise. A realtime audience recalculates membership as data arrives. A batch audience recalculates on a schedule, which means the answer you get may describe the customer's world several hours ago. If your feature acts on an audience immediately, surface this. A customer who picks a batch-computed audience for a realtime trigger has made a mistake your interface could have prevented.
type returns USERS, ACCOUNTS, or LINKED. For a B2B product this is the difference between a feature working and quietly targeting the wrong thing. An ACCOUNTS audience is a set of companies; a USERS audience is a set of people. Filter on this rather than asking the customer to infer it from an audience name.
definition returns the membership rule itself. Names decay: an audience called "High Intent Q3" was defined by someone who has since left, and nobody is certain what it selects for any more. Showing the rule alongside the name lets a customer confirm they are picking what they think they are picking.
size returns the number of profiles currently in the audience. Useful as a sanity check in your interface, and useful as a guard: a customer about to run a campaign against an audience of four has probably chosen the wrong one.
slug is the data-plane handle that identifies membership in profiles and destinations. Where id is what you store, slug is what you match on when you follow an audience into other parts of the customer's stack.
is_active tells you whether the audience is live in the customer's CDP. An inactive audience is one they have paused or retired, and it usually should not appear as a choice.
Handling coverage that varies by platform
Each CDP's own API determines what Unified can return, so a field that exists on the object may not be available on every integration. This is not something to design around after the fact. It is a first-class part of building on any category with a wide integration surface, and three habits handle it.
Check support before you scope. Field support, webhook support, and list parameters are documented per integration. If your feature depends on definition, confirm which platforms return it before you promise it.
Treat 501 as information, not failure. Where a platform's API does not support an object, the request returns 501 Not Implemented rather than an empty list. That distinction matters: an empty list means the customer has no audiences, and a 501 means this platform cannot tell you either way. Show those differently.
try {
const segments = await sdk.cdp.listCdpSegments({ connectionId });
return renderPicker(segments);
} catch (error) {
if (error instanceof errors.SDKError && error.statusCode === 501) {
return renderUnsupported();
}
throw error;
}
SDKError is thrown for every 4XX and 5XX response and carries statusCode alongside the message and body, so the same pattern handles authorization failures and rate limiting next to the unsupported case.
Render what returns. Build the interface so name and id are sufficient, and every other field is an enhancement that appears when the platform provides it. A picker that degrades gracefully works across the whole category. A picker that assumes definition works on a subset of it.
Stay current when the customer changes an audience
A customer who builds a new audience expects to see it in your product without being told to refresh anything. Rather than re-reading the full list on a schedule, subscribe to changes and let Unified detect them and deliver events to your endpoint.
{
"connection_id": "5de520f96e439b002043d8dc",
"hook_url": "https://yourapp.com/webhooks/cdp",
"object_type": "cdp_segment",
"event": "created",
"interval": 5,
"webhook_type": "virtual"
}
Subscribe separately for created and updated. The interval field sets how often Unified checks for changes, and can be set as low as one minute on paid accounts and 60 minutes on free ones. webhook_type selects between virtual, where Unified detects changes and delivers events, and native, where the platform's own webhooks are used; which is available depends on the integration.
Passing include_all on the create request sends your server all existing audiences on the first run, which backfills without a separate import path.
Each subscription carries a read-only is_healthy field. Read it as part of your own monitoring, so a subscription that has stopped delivering surfaces in your product rather than in a support ticket.
Segment has the widest webhook coverage of any object in the CDP category, but it is not universal. Confirm your customers' platforms on the Supported Integrations page, and fall back to a scheduled read where a platform has no webhook events.
Read only what changed
For the platforms where you are reading on a schedule, updated_gte returns only records modified since a timestamp you supply.
const changed = await sdk.cdp.listCdpSegments({
connectionId,
updated_gte: lastSyncedAt,
sort: 'updated_at',
order: 'asc',
limit: 100,
});
Sorting ascending on updated_at means the last record in the response carries the timestamp for your next call, so you do not maintain a cursor. The same parameters work on every object in the category.
Two details make the paging loop simpler than it looks. offset is zero-based on every integration regardless of how that platform pages internally, so you count records rather than pages. And you know you have reached the end when a response returns fewer records than the limit you asked for, which is the exit condition for the loop. Most list endpoints cap at 100 records per page, and not every integration supports every pagination parameter, which is recorded on the Feature Support tab of the integration's page in app.unified.to.
→ Start your 30-day free trial → Book a demo
Frequently asked questions
Should I read audiences at request time or synchronize them into my own database?
Both, for different jobs. Read at request time when you need the current state of a record you can already identify, such as the audience a customer has just selected. Synchronize when you need to search, filter, or join across records, because the query parameter filters only on email or name, and anything beyond that has to happen in a database you control. Unified.to recommends synchronizing if you plan to run queries, using webhooks as the mechanism, which is the same subscription described above. The read path and the sync path are not alternatives: the webhook that keeps your picker current is also what populates the copy you query against.
What happens on a platform that returns only a name and an ID?
Your picker still works. id and name are returned by every integration supporting the Segment object, and a picker built to treat the remaining fields as optional will render correctly on a minimal platform and richly on a complete one. This is why the coverage question is an interface decision rather than a blocker.
How do I tell whether an audience is safe to act on in real time?
Read compute_mode. REALTIME means the CDP recalculates membership as data arrives. BATCH means it recalculates on a schedule set inside the customer's platform, so membership can be hours out of date regardless of how current your read is. Unified returns the audience as it exists right now; compute_mode tells you how recently the CDP itself worked it out.
Is a batch-computed audience still worth reading?
Yes, for anything that is not time-critical. Reporting, list building, campaign setup, and account-level targeting are all well served by a batch audience. The distinction matters for triggers that fire on a user action, where acting on stale membership produces a visibly wrong result.
Author
Written for Unified.to by Mallory Greene
About the author: Mallory Greene is a writer specializing in generative engine optimization (GEO) and content, through her practice Search Everywhere. She covers integration infrastructure and technical content across Unified.to's technical content library. Based in Toronto.