> ## 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.

# Publish a segment to an external destination on a schedule

> Register an audience activation — a saved segment pushed to an ad network or a webhook sink on a refresh cadence — then create, run, and monitor it.

# Audience activation pipelines

A saved segment answers "who are we talking about." An activation answers "where does that audience keep landing." An activation is a registered connection that publishes a segment (or list) to an external target — a paid-media network (Google Ads, Meta, TikTok, LinkedIn, Snapchat, Pinterest, Reddit, The Trade Desk, Criteo) or your own HTTPS webhook sink — on a schedule you control. Orbit handles the hashing, batching, and signature; you wire the destination, approve the run if your policy requires it, and watch the history.

For one-off pulls, the [ad-hoc CSV export](/guides/audience-export-adhoc) is the right tool. For a recurring publish, an activation is.

## 1. What an activation contains

One destination is one row of config:

* **The source segment id** the run resolves membership from.
* **The target** — an ad platform's audience id (Custom Audience, user list, DMP segment) reached over a Nango OAuth connection, or your webhook URL, signed per delivery.
* **Identifiers per member** mapped to destination fields. Ad networks take hashed email/phone/name/address/mobile-id columns; a webhook receives the identifiers you forward. Either way, every member needs at least one strong identifier (email, work email, phone, or mobile advertising id) — rows with only a name are rejected, not silently dropped.
* **A refresh cadence** (`schedule_minutes`) — null means manual runs only.
* **Run history and status** per activation, per destination (`ok`, `partial`, `failed`, `skipped`).

Every run is audited, and ad-network pushes hash PII with SHA-256 before it leaves Orbit.

## 2. Prerequisites

Before the first run:

* A **saved segment** (`POST /api/v1/contacts/segments`) — set `auto_refresh` so membership the activation re-reads is current.
* **Target credentials**: an OAuth connection to the ad network (registered in Orbit as a Nango connection id), or an HTTPS webhook URL you control.
* **Roles**: owner, admin, or developer on the API key for every config and activate call.
* Consent and suppression hygiene: members flagged `consent: false`/`suppressed: true` fail the pre-activation gate below its thresholds — keep those flags honest at ingest.

## 3. Create the activation config

Wire the destination once; only supplied keys change on each PATCH.

```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_churn_tier1",
    "schedule_minutes": 1440
  }'
```

| Field                 | Description                                                                                                                   |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `enabled`             | Activations refuse to run until this is true; going live without `audience_id` and `nango_connection_id` is rejected (`409`). |
| `nango_connection_id` | OAuth connection identifier — an id, not a secret.                                                                            |
| `audience_id`         | The destination list on the ad network.                                                                                       |
| `segment_id`          | Which saved segment the scheduled run reads. Optional per-run override: the activate call carries its own `segment_id`.       |
| `schedule_minutes`    | Recurring cadence in minutes (daily = 1440). Omit for manual-only.                                                            |
| `holdout_pct`         | Optional control-holdout percent \[0, 50] for incrementality measurement.                                                     |
| `preflight`           | Optional thresholds for the pre-activation gate (minimum audience size, coverage, consent).                                   |

For a webhook sink instead of an ad network:

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/cdp/audience/webhook \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "enabled": true,
    "target_url": "https://edge.exampleco.io/orbit-segment-sync",
    "signing_secret": "wss_live_example_rotate_me",
    "segment_id": "seg_winback_30d"
  }'
```

The secret is encrypted at rest and never returned by `GET`; deliveries are signed `X-Orbit-Signature: t=<unix-sec>,v1=<hmac-sha256>`, the same scheme your existing Orbit webhook verifier already uses. Pass empty string to clear it.

## 4. Run an activation

Push the current membership explicitly, or refresh a suppression (`remove`) run:

```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_churn_tier1",
    "operation": "add",
    "members": [
      { "email": "ada@example.com", "phone": "+14155550123" },
      { "madid": "a1b2c3d4-e5f6-47a2-91c1-55d0ab2d9c01" }
    ]
  }'
```

Members resolve to this profile shape: `email`, `email_work`, `phone`, `first_name`, `last_name`, `city`, `state`, `zip`, `country`, `madid`. A request carries up to 10,000 members; a segmentation job sends batchfuls in one run.

Dry-run first with `POST /api/v1/cdp/audience/preflight/:platform` — same verdict, nothing dispatched. If a gate blocks and the threshold is genuinely fine for your destination, send `override_preflight: true` (audited) — unless this org has maker-checker on, in which case the run queues for approval: submit without an `approval_id` to enqueue, an owner/admin approves via `POST /api/v1/cdp/audience/approvals/:id/decision`, then re-submit with the returned `approval_id`.

Webhook variant:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/cdp/audience/webhook/activate \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "segment_id": "seg_winback_30d",
    "operation": "add",
    "members": [
      { "user_id": "u_4821", "email": "ada@example.com", "traits": { "tier": "vip" } }
    ]
  }'
```

Your sink receives one signed POST of event type `cdp.segment.activation` with `segment_id`, `operation`, `member_count`, and the `members` array.

## 5. Read status and failures

Each destination reports `last_activation_status`; every run lands in a capped history (newest first, 50 entries).

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/cdp/audience/activations \
  -H "X-API-Key: dv_live_sk_your_key_here"

curl https://api.orbit.devotel.io/api/v1/cdp/audience/activations/4e3b9c1d-... \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

The run detail shows `requested` / `matched` / `skipped` member counts, batches dispatched, an `error` message when a dispatch call failed, and per-member rejections (index + reason, never PII). Retry a non-`ok` run with `POST /api/v1/cdp/audience/activations/:id/retry` and a corrected member batch — it is recorded as a new run linked to the original.

Status meanings:

* `ok` — every batch the network accepted.
* `partial` — some batches failed; the member-level detail names why.
* `failed` — nothing accepted (quota breach, revoked token).
* `skipped` — nothing matchable, or no connection wired at dispatch time.

## 6. Cadence and delta updates

Setting `schedule_minutes` enrolls the destination in a scheduled sweep (a background worker that ticks every 15 minutes and runs a destination when its cadence has elapsed). Set `segment auto_refresh` so a scheduled run always re-resolves fresh membership — spin correspondingly.

The delta weapon is `operation`:

* `add` — newest members; acquisition or seeding a lookalike.
* `remove` — suppression; drop existing customers from prospecting spend.

Lean on successive `add`/`remove` deltas rather than full re-uploads: the destination stays diff-shaped, and suppression runs are the cleanest way to stop spend on won-back customers.

## 7. Patterns

* **Churn-tier → ads.** A churn-risk segment refreshes daily into a Google customer-match list; auto-suppress your own customers from prospecting.
* **VIP → email or ads.** A high-LTV segment synced on a slow cadence (weekly) — refresh noise is worth less than accuracy here.
* **Win-back → webhook sink.** Fan a lapsed-customer segment into your in-house orchestration endpoint; stack it on top of [segment-triggered journeys](/guides/segment-triggered-journeys) so onboard entry and offboard suppression stay on one membership definition.
* **Incrementality.** Set `holdout_pct` on the destination so the report can separate exposed conversions from a randomized control arm.

## 8. Troubleshooting

* **`409 CONNECTION_NOT_CONFIGURED` / `PLATFORM_NOT_ENABLED` / `AUDIENCE_NOT_CONFIGURED`.** The destination is not fully wired — enable it with an `audience_id` and (for ad networks) a `nango_connection_id`. Direct API PATCHes get the same precondition the dashboard enforces.
* **`422 AUDIENCE_PREFLIGHT_BLOCKED`.** Gate rejected the batch — too small, low identifier coverage, or consent/suppression violations. The failing check ids come back in the response; re-check `POST .../preflight/:platform`, then adjust the segment, the thresholds, or override deliberately.
* **`400` naming a field.** Off-schema input — the message is field-prefixed (e.g. `holdout_pct: ...`), so read which key was refused.
* **Run status `skipped` with `dispatch_skipped` equal to your batches.** No connection was resolved at dispatch — a cleared or never-wired `nango_connection_id`. Re-wire and retry the run.
* **Run status `failed`, `error` mentions quota / auth.** The ad-network call was rejected (quota breach or a stale OAuth token). Refresh the connection token, wait out the quota window, then `POST .../retry`; only `ok` runs are refused as already-dispatched.
* **Zero matched members.** The batch arrived with no strong identifier per row (name+address only). Resolve before activate — either the segment filter picks up empty profiles, or the field mapping at ingest is broken. A blank export downstream typically means exactly this.
* **Approval queue hangs.** Maker-checker is on (`GET .../approvals/settings` shows `require_activation_approval: true`): request → approve → re-submit with the approval id. A supervisor cannot self-approve — a second person must sign.

Tenant-owned controls like consent checks and suppression lists gate every activation, and cleartext never reaches an ad network — digests only, counts only in the log.

## See also

* [CDP audiences: build segments](/guides/cdp-segments) — the segment recipe every activation reads
* [Build a segment-triggered journey](/guides/segment-triggered-journeys) — onboard offboard flows on segment membership
* [Ad-hoc audience CSV export](/guides/audience-export-adhoc) — when you need the rows once, not a pipeline
* [Ads API reference](/api-reference/ads) — Meta ads loop (campaign, attribution)
* [Reverse-ETL warehouse exports](/guides/reverse-etl-warehouse-exports) — publish segments to a warehouse instead
