Skip to main content

CDP Audiences

This guide walks the full path from raw customer data to an audience you can spend money against: what profiles the CDP stores, how to build a segment from attributes and behavior, how to stamp derived traits onto contacts, how B2B accounts get scored, and how to push the result to an ad network. It closes the loop opened by the end-to-end campaign guide — the audience you target a campaign with is built here.

1. What the CDP stores

The CDP maintains two things per contact and per account:
  • Profiles and identity. Every contact carries resolved identifiers (email, phone, device ids), built-in attributes (name, company, lifecycle stage), your custom fields, and derived data — tags, scores, and computed traits. Contacts in the same company roll up into an account for B2B scoring.
  • Events. Behavioral activity arrives through the tracking surface (POST /api/v1/cdp/track) and file ingest (POST /api/v1/cdp/file-ingest for CSV batches): page views, link clicks, purchases, product events. Segments filter over this behavioral history with operators like performed_event and event_count, so the cleanliness of your event feed determines the quality of every audience downstream.
If your contacts or events are thin, no segment below will fix that — ingest first, then segment. The event schemas API (/api/v1/cdp/event-schemas) lets you govern which event names and properties are valid, so a renamed event does not silently empty a segment.

2. Build a segment

A segment is a saved audience definition: a filter over contact attributes, custom fields, segment membership, and behavioral events. Create one with POST /api/v1/contacts/segments:
Filter-building rules worth knowing before you ship one:
  • Conditions combine with and / or groups, nested up to three levels. A condition is { "field", "op", "value" } — the operator vocabulary includes comparisons (equals, gt, in, contains), time-window membership (in_last), and behavioral verbs (performed_event, did_not_perform_event, event_count, performed_sequence for ordered journeys — “did A then B within 72h”).
  • Events are referenced with the event.<event name> field prefix — pair it with a behavioral operator. Custom fields use custom_attribute.<key>.
  • Segments can reference other segments ({ "field": "segment_id", "op": "in_segment", "value": "<segment_id>" }) so a base “active customers” segment is defined once and reused everywhere.
  • auto_refresh keeps membership materialized on a schedule (12 hours up to monthly). A campaign reads the materialized membership at send time, so your filter edits do not need a send-time re-evaluation.
Size the filter before saving it: POST /api/v1/contacts/segments/preview evaluates a filter against live data and returns the match count without persisting anything, and POST /api/v1/contacts/segments/estimate projects size for planning. If you would rather describe the audience in words, POST /api/v1/contacts/segments/from-prompt converts a sentence (“enterprise customers who clicked but did not convert this month”) into a validated filter you can review and save. For the full endpoint list — membership, exports, snapshots, overlap — see the Segments API reference.

3. Computed traits

Segments filter over facts that already exist. A computed trait manufactures a new fact: you author a deterministic predicate, and contacts whose data matches get the rule’s output_tag stamped onto their profile. That tag then behaves like any other attribute — segmentable, personalizable, exportable. Create a rule with POST /api/v1/cdp/computed-traits:
How the predicate language works:
  • Facts. Predicates read a fact bag per contact. Platform-supplied facts include predicted_ltv, churn_risk_score, lifecycle_stage, engagement_score, last_seen_at, created_at, total_revenue, email_optin, sms_optin — plus any custom-field key you pass; the vocabulary is open.
  • Leaves. A leaf compares one fact: numeric (eq, gt, between, …), string (contains, in, …), boolean, date (within_days, older_than_days — relative windows so “last 7 days” needs no timestamp math), and exists / not_exists for presence checks.
  • Combinators. Leaves nest under all_of, any_of, and not, up to a bounded depth so a rule stays auditable.
  • Missing facts are safe. A missing or wrong-typed fact evaluates a leaf to false (except not_exists) — a rule never stamps a tag off absent data.
Two operational notes. First, assignments are maintained by a scheduled evaluator that sweeps enabled rules and syncs tags onto contacts; a rule you save tonight is reflected on the next sweep. Second, POST /api/v1/cdp/computed-traits/:id/evaluate runs a rule against a fact bag you pass inline without writing anything — use it from a rule-builder UI to answer “would this contact match?” before enabling.

4. Account scoring

Per-contact scores (churn risk, intent, propensity, LTV) already exist on the profile. For B2B motions you need the same math at the account level — one number per company. The scoring surface computes that on the fly, no ingestion or storage required:
Every contact with a company value is grouped into an account, and per-contact scores roll up into:
  • account_churn_risk — value-weighted churn (a high-LTV contact’s churn moves the account number more than a trial user’s). Accounts with no attributed value report no churn number rather than masquerading as a top risk.
  • expansion_propensity and intent_score — upsell and intent signals averaged across the account’s members.
  • total_value_cents — the account’s aggregate member LTV.
  • firmographic_band and health_segment — size bucket (soloenterprise) and the ABM triage label (at_risk, expansion, engaged, stable) you can target campaigns on.
GET /api/v1/cdp/accounts/score?account=<name> drills into one account with the per-contact member breakdown — which individual contacts are driving the account’s risk, and what stage each is in.

5. Retention segments on churn risk

Per-contact scores are segmentable numeric fields, not just a sortable list. To build a self-updating at-risk audience, put churn_risk in a segment filter:
Three rules to keep retention audiences honest:
  • Unscored contacts never match. A contact with no score yet reads as no score, so numeric rules fail-closed — a win-back flow cannot fire on a guess. Give newly imported contacts a scoring cycle (the daily re-scoring pass) before relying on a score filter.
  • Preview before you activate. POST /api/v1/contacts/segments/preview returns the match count against live scores, so a threshold of 0.6 versus 0.8 is a two-call comparison, not a guess.
  • Auto-refresh keeps it live. Scores move daily; a churn_risk > 0.6 segment with auto_refresh re-evaluates after each re-scoring pass. Contacts crossing the boundary enroll in segment-triggered flows (campaigns — win-back enroll-on-entry, suppress-on-exit).
Prefer a snapshot over a threshold? Score the trained churn-propensity model’s population and send the top of the list straight to a segment:
The response carries the created segment and member count — a static snapshot of the ranking you just previewed, ready for campaigns, ad-network sync, or export. If the model cannot be fit yet the response is a 422 with the fit report (missing outcomes, minimum sample sizes parsed back to you); let resolved lifecycle outcomes accumulate and retry. GET /api/v1/contacts/scores lists every contact’s churn risk, intent, propensity, lifetime-value estimate, and health label with cursor pagination — sort descending on churn_risk and filter by segment_label to power an at-risk review list.

6. Activate an audience

Building an audience is half the job; activation pushes it somewhere it earns spend. The CDP’s activation surface maps a segment to an ad-network audience and syncs membership:
Supported platforms: Meta, Google, TikTok, LinkedIn, Snapchat, Pinterest, Reddit, The Trade Desk, and Criteo. Two things to know about what leaves your tenant:
  • Hashed, not cleartext. Identifiers (email, phone, name, address columns) are normalized and SHA-256-hashed before dispatch — the ad networks match hashed columns against their own user graphs, so cleartext PII never leaves the platform, is never logged, and is never persisted on the destination config. The one exception is the mobile advertising id (IDFA/GAID), which the ad networks match raw by contract.
  • Use it for suppression too. operation: "remove" pushes a segment as an exclusion — suppress active customers from acquisition spend, or suppress recent purchasers from a promo audience.
GET /api/v1/cdp/audience/activations returns the run history (counts only) so you can confirm what synced, and GET /api/v1/cdp/audience/config shows every platform mapping with its last-run status. Audiences also leave the CDP without ad networks: a saved segment is directly targetable by campaigns and flows, exportable to CSV (GET /api/v1/contacts/segments/:id/export.csv), and syncable to a warehouse via reverse ETL. Two disciplines sit underneath everything above; audiences built on duplicated persons or unconsented contacts are wasted spend and a compliance risk. Identity resolution merges duplicate contact records that represent one person — the same human arriving via web form, mobile app, and a CSV import, each with a different identifier. GET /api/v1/cdp/identity/merge-candidates lists proposed merges for review. Merging matters to this guide because segments and scores are only as accurate as the identity layer: merge first, then your churn scores and segment counts stop double-counting. Consent gates whether you may use a profile at all. GET /api/v1/cdp/consent/:contactId reads a contact’s current consent state per channel, and POST /api/v1/cdp/consent/check answers “may I contact this person on this channel for this purpose?” — the same verdict the marketing-send pipeline enforces before dispatch. When a contact revokes consent, the revocation propagates across downstream stores so a segment refreshed tomorrow does not re-include someone who opted out today. Keep audience definitions consent-aware where the channel requires it (sms_optin, email_optin are facts you can predicate on, as the trait example above shows), and rely on the send-time gate as the backstop.

See also