> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# First CDP activation, end to end: ingest → resolve → segment → ship

> One-sitting walkthrough of the full CDP loop: wire a signed source, resolve identity, build a segment over resolved events, activate it to an ad network or webhook sink, and confirm the sync — with the lag budget to troubleshoot by.

# First CDP activation, end to end

The CDP has four phases — ingest, identity resolution, segmentation, activation — and each has a dedicated guide that goes deep on its own surface. What none of them do alone is walk you through one continuous pass: send an event, watch it stitch to a person, build an audience over that history, and push the audience to a destination you can spend against. This tutorial does that one pass, start to finish, in one sitting.

Do this walkthrough first. Once the loop is live once, the phase guides become your deepening references:

* [CDP integration console: source and destinations](/guides/cdp-source-and-destinations) — the full ingest-secret and destination lifecycle.
* [CDP data catalog and identity rules](/guides/cdp-data-catalog-and-identity-rules) — match shapes, priorities, survivorship.
* [CDP audiences: build segments](/guides/cdp-segments) — the complete filter, trait, and scoring vocabulary.
* [Audience activation pipelines](/guides/audience-activation-pipeline) — cadences, deltas, holdouts, troubleshooting per platform.
* [CDP reverse ETL and warehouse exports](/guides/cdp-reverse-etl-and-warehouse-exports) — when the destination is a warehouse, not an ad network or webhook.

## Why a single end-to-end pass matters

Each phase guide assumes the others exist. Read in isolation they leave you stitching the loop yourself: the source guide tells you how to accept events but not what to do once they land; the segments guide assumes resolved profiles and clean events; the activation guide assumes a segment worth publishing. New CDP workspaces fail in the gaps between guides — events ingested but never stitched, segments built over unresolved duplicates, activations publishing thin membership nobody diagnosed upstream.

Run this one pass against a test source and a test destination before you turn on real traffic. When something misbehaves later, you re-enter the phase guide for that phase — but you re-enter it knowing which phase is broken, because you have seen the whole loop healthy once.

## 1. Prerequisites

Have all three of these before the first event:

1. **A published tracking plan.** Declare the events your app will send and the property shape per event, in `soft` enforcement mode, on the [tracking plan](/guides/cdp-tracking-plan) surface. Soft mode logs violations without rejecting events, so a new producer can never stall on a mismatch while you iterate. Tighten to `strict` later.
2. **One connected source.** An ingest secret minted under **Integrations → CDP → Source (inbound)** and deployed to your app — the [source and destinations guide](/guides/cdp-source-and-destinations) walks minting and rotation. Your app posts signed events to `https://api.orbit.devotel.io/cdp/v1/<ingest_id>/track`.
3. **One destination with credentials.** An ad-network audience reached over an OAuth connection, or an HTTPS webhook sink you control. Wire the credentials before you need them — the activation phase only maps a segment onto a destination that already authenticates.

Also have an API key with an **owner, admin, or developer** role. Every mutation in this walkthrough (rules, segments, activations) is gated to those roles; reads accept more.

## 2. Ingest a source

Pick a source kind on **Integrations → CDP** — Segment, RudderStack, a generic webhook, or your own SDK posting directly. Mint the ingest secret from the **Source (inbound)** tab, deploy it to the app, and sign every request per the [ingest-signing guide](/guides/cdp-ingest-signing): the three headers `X-Orbit-CDP-Timestamp`, `X-Orbit-CDP-Nonce`, and `X-Orbit-CDP-Signature` over `<timestamp>.<nonce>.<raw body>`, keyed by the secret plaintext.

Fire one `track` event from the app. Then confirm it landed — never assume:

1. Open **Integrations → CDP → Event debugger**.
2. Filter **Type = track** and match your event name.
3. Expand the row and read the exact `properties` and `context` the gateway stored.

If the row is missing, the failure is at the signature layer (a 401 the app swallowed) or the tracking plan is rejecting the payload — the [event debugger and DLQ guide](/guides/cdp-event-debugger-and-dlq) walks both, including how to read a spilled or lost payload body. Do not proceed until one real event is visible here.

## 3. Resolve identity

Events arrive carrying whatever `userId` your SDK sent. Whether that stitches onto a contact — and which contact — is decided by the identity rules, not by the event itself. The default resolves `userId` against external IDs only; an email- or phone-shaped `userId` does not stitch until you enable that match shape.

1. Open **Integrations → CDP → Identity resolution** and review the match shapes: `External ID` (seeded, priority 100), plus opt-in `Phone`, `Email`, and `Anonymous ID`.
2. If your events carry email or phone as the identity, **simulate first** on **Audience → Identity resolution** — the dashboard refuses to save a change to those shapes until a simulation has run in your session, because an over-broad shape stitches wrong people together irreversibly. Read the estimated merge count; if it over-merges, narrow the shape or raise its priority.
3. Save the rule set back on the CDP page, then set the survivorship policy (which value wins a merge per field) in the same builder.
4. Confirm resolution landed: open **Audience → Identity resolution** and check that your test event's identity folded onto one contact, not a review-queue duplicate. The [data catalog and identity rules guide](/guides/cdp-data-catalog-and-identity-rules) covers priorities, the simulation gate, and the policy-versus-review-queue split.

Skip nothing here: every segment below reads resolved contacts, so unresolved duplicates survive into every audience count you build.

## 4. Build the segment on resolved events

With events landing and identity stitching, build the audience. A segment is a saved filter over contact attributes, custom fields, and behavioral events; create with `POST /api/v1/contacts/segments`. Two worked examples — a plain filter and a SQL trait feeding it.

### A worked behavioral filter

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/contacts/segments \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Activated trial users",
    "description": "Trials who completed onboarding in the last 14 days",
    "filters": {
      "and": [
        { "field": "lifecycle_stage", "op": "equals", "value": "trial" },
        { "field": "event.Onboarding Completed", "op": "performed_event", "value": { "within": "14d" } },
        { "field": "event.Payment Failed", "op": "did_not_perform_event", "value": { "within": "14d" } }
      ]
    },
    "auto_refresh": true,
    "auto_refresh_interval_minutes": 720
  }'
```

Three things to notice. Events are referenced with the `event.<name>` prefix and pair with a behavioral operator — `performed_event`, `did_not_perform_event`, `event_count`, or `performed_sequence` for ordered journeys. The `within` window is relative, so "last 14 days" needs no timestamp math. And `auto_refresh` materializes membership on a cadence — an activation in the next step reads that materialized membership, so set it.

Size the filter before you save it. `POST /api/v1/contacts/segments/preview` evaluates the same filter against live data and returns a match count without persisting; a count of zero or "the whole tenant" both mean the filter is wrong, not the data.

### A concrete SQL trait feeding the segment

When the fact you need already exists as warehouse SQL — a finance-owned LTV view, a dbt model — adopt it as a [SQL trait](/guides/cdp-sql-traits) instead of rebuilding it in the predicate DSL. One read-only `SELECT` returning `(user_id, trait_value)`, run on your schedule, stamps each value into the contact's traits map:

```sql theme={null}
SELECT user_id,
  CASE
    WHEN SUM(amount_usd) >= 1000 THEN 'high'
    WHEN SUM(amount_usd) >= 100  THEN 'mid'
    ELSE 'low'
  END AS trait_value
FROM analytics.orders
WHERE ordered_at >= DATE_SUB(CURRENT_DATE(), INTERVAL 365 DAY)
GROUP BY user_id
```

Once the trait materializes, it is segmentable like any attribute — `{ "field": "traits.ltv_tier", "op": "equals", "value": "high" }` combines freely with the behavioral conditions above. The full engine-choice tree (DSL predicate, SQL, AI) is on the SQL-traits page; the complete filter vocabulary is the [segments guide](/guides/cdp-segments).

## 5. Activate to a destination

Activation publishes the segment somewhere it earns spend — an ad network's audience or your webhook sink — on a cadence you control. Walk it in two calls, per the [activation pipeline guide](/guides/audience-activation-pipeline).

Wire the destination once. For an ad network (Google shown; Meta, TikTok, LinkedIn, and the rest of the catalog behave the same):

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/cdp/audience/config/google \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "nango_connection_id": "conn_googleads_7d2e",
    "audience_id": "ul_91428",
    "segment_id": "seg_activated_trials",
    "schedule_minutes": 1440
  }'
```

For your own HTTPS sink instead, PATCH `/api/v1/cdp/audience/webhook` with `target_url` and a `signing_secret`; deliveries arrive signed `X-Orbit-Signature: t=<unix-sec>,v1=<hmac-sha256>`, the same scheme your existing Orbit webhook verifier already checks. Either way, identifiers are SHA-256-hashed before dispatch to ad networks — cleartext PII never leaves the tenant.

Run the first activation explicitly rather than waiting for the schedule:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/audience/activate/google \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "seg_activated_trials",
    "operation": "add"
  }'
```

Dry-run it first with `POST /api/v1/cdp/audience/preflight/google` whenever the verdict is uncertain — same gate, nothing dispatched. The pre-activation gate blocks undersized audiences, low identifier coverage, and consent or suppression violations; the failing check ids come back in the response. If this org has maker-checker on, the run queues for a second person's approval — submit, have an owner approve, re-submit with the returned approval id.

## 6. Readback and troubleshooting

Confirm the run landed, then learn the lag budget so you know healthy from stuck.

**Confirm.** `GET /api/v1/cdp/audience/activations` lists run history; `GET /api/v1/cdp/audience/activations/:id` drills one run with `requested` / `matched` / `skipped` counts and per-member rejection reasons (never PII). Status `ok` with `matched` near `requested` is healthy; `partial` names its bucket; `skipped` means nothing matched or no connection resolved at dispatch. For a warehouse destination instead of an ad network, read the same verdicts on the [sync run log](/guides/cdp-sync-run-log) — per-run `attempted` / `delivered` / `rejected` / `failed` and error classes (`auth`, `validation`, `rate_limit`, …) that decide the retry loop.

**The lag budget.** Each phase has its own expected delay; a complaint only becomes a bug when one phase exceeds its budget:

| Phase                           | Expected lag                                    | Where to check                                                                                                        |
| ------------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- |
| Event → ingest visible          | seconds                                         | Event debugger; a missing row within seconds means signature or tracking-plan rejection.                              |
| Ingest → identity stitched      | minutes                                         | The event's resolved `contact_id` on the debugger row; identity rules that never matched look like unstitched events. |
| Stitch → segment membership     | one auto-refresh interval                       | The segment's materialized membership; a manual `preview` shows the live count immediately, so compare the two.       |
| Membership → destination landed | one schedule tick (sweeps run every 15 minutes) | The activation run history or sync run log; `last_activation_status` on the config.                                   |
| Destination freshness SLA       | 26 hours per destination                        | The sync-run-log `overdue` flag flips past it.                                                                        |

Reading the table top-down converts "the audience is wrong" into "phase N is over budget" in one pass. Then re-enter that phase's dedicated guide — source and destinations, identity rules, segments, activation, or reverse ETL — with the failure already localized.

**The four failure shapes you will actually meet.** A `409 CONNECTION_NOT_CONFIGURED` on activate means the destination is not fully wired (missing `audience_id` or connection id). A `422 AUDIENCE_PREFLIGHT_BLOCKED` names its failing gate check — resize the segment, fix identifier coverage, or override deliberately. Zero matched members means rows arrived with no strong identifier — fix the mapping at ingest, not the activation. A `partial` run whose drilldown shows `validation` buckets with "missing or empty upsert identifier" means the upsert key is unmapped — fix the field mapping, then rerun. All four are covered per-phase in the guides linked above.

## See also

* [CDP integration console: source and destinations](/guides/cdp-source-and-destinations) — phase 1 deepening: secrets, destination kinds, circuit breakers
* [CDP ingest signing](/guides/cdp-ingest-signing) — the HMAC contract your producers implement
* [CDP data catalog and identity rules](/guides/cdp-data-catalog-and-identity-rules) — phase 2 deepening: match shapes, simulation, survivorship
* [CDP audiences: build segments](/guides/cdp-segments) — phase 3 deepening: the full filter, trait, and scoring vocabulary
* [Audience activation pipelines](/guides/audience-activation-pipeline) — phase 4 deepening: cadences, deltas, holdouts, per-platform troubleshooting
* [CDP reverse ETL and warehouse exports](/guides/cdp-reverse-etl-and-warehouse-exports) — publish to a warehouse instead of an ad network
* [CDP sync run log](/guides/cdp-sync-run-log) — drill failed warehouse sync rows by error class
