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

# CDP clean rooms: shared analytics without data egress

> How Orbit's partner data clean room measures the shared-audience overlap between your first-party audience and a partner's without either side exposing customer rows — hashing contract, k-anonymity suppression, dashboard walkthrough, and the operator workflow end to end.

# CDP clean rooms

A clean room lets two parties answer one question — *how much of your audience
is also ours?* — **without either side ever seeing the other's customer
list**. Orbit's clean room is part of the CDP: it compares sets of hashed
identifiers, returns only counts and rates, and withholds every number when
the overlap is small enough to single out a person. The result is
co-marketing-sized analytics — an overlap count, a match rate per side, a
similarity score — with **zero data egress**: no customer rows move between
you and your partner, and none are written to storage.

This page covers the model and the day-to-day operation of it: what a clean
room is on Orbit, how the **Clean room** tab on the CDP page works, what the
match engine will and will not return, the operator workflow from first share
to closing a collaboration, and how the consent you already record travels
with the data into every match.

## What a clean room is on Orbit

In Orbit's CDP your first-party audience already resolves to profiles with
identifiers ([identity resolution](/concepts/cdp-identity-resolution)) and
groups into segments ([CDP segments](/guides/cdp-segments)). A **clean room**
is the sandboxed sharing surface on top of that audience: it lets you run a
join against a partner's audience — a retailer against a brand, a brand
against a media publisher — and get an evaluation back, without revealing the
underlying rows on either side.

Three properties define the room:

* **Identifiers, never rows.** The only thing compared is a set of SHA-256
  digests of email and phone identifiers. Names, addresses, purchase
  histories, and profile traits never enter the match.
* **Aggregations only.** The result of a match is counts and rates — the
  overlap size, each side's match rate, a Jaccard similarity score — plus the
  intersection digests, which are digests **you already hold** (they came from
  your own list). There is no query shape that returns a partner's rows.
* **k-anonymity.** Below a minimum overlap threshold the whole quantitative
  result is suppressed, so a one-member probe can never confirm a single
  person's membership in the partner's audience.

The compute is **stateless**: the match runs in-request, the inputs are
discarded when the response is sent, and the only persistence is a
counts-only audit entry (overlap size and identifier counts — never an
identifier or a digest). Nothing about the partner's set is stored, and
nothing about yours is either beyond what your CDP already holds.

### The hashing contract both sides follow

A match only intersects when both sides hash identically, so the contract is
fixed and public:

* **Email** — normalized (trimmed, lowercased) exactly the way
  [ad-audience exports](/guides/audience-activation-pipeline) normalize before
  hashing, then SHA-256. A digest you produce for an ad network and a digest a
  partner produces for a clean room line up.
* **Phone** — canonicalized to E.164 **with** the leading `+`, then SHA-256.
  A partner hashing bare digits without the `+` simply never matches — a
  data-prep error on their side, never a privacy leak.
* **Server-side hashing** — when you paste cleartext identifiers on your own
  side, the server normalizes and hashes them in memory. Cleartext never
  leaves the process, is never persisted, and is never logged. Your partner
  supplies **pre-computed digests only** — they never send cleartext at all,
  which is what makes the collaboration privacy-safe.

## The Clean room tab

The surface lives at **Integrations → CDP → Clean room**. Everything it does
is also available server-to-server on
`POST /api/v1/cdp/clean-room/match` (owner, admin, or developer role with the
`contacts:read` scope), so your CI or an agency partner's tooling can drive
the same match without a dashboard.

The tab is a two-pane share configuration plus a result card:

1. **Your audience.** Paste your identifiers — emails or phones, one per
   line (commas also work). A token containing `@` is treated as an email,
   anything else as a phone. These are cleartext in the textarea only; the
   server hashes them and never stores them. The counter shows how many
   identifiers you have staged.
2. **Partner digests.** Paste the SHA-256 digests your partner sent you, one
   per line. They are lowercased and deduplicated as you paste — this is
   canonicalization, not validation; anything that is not a 64-character hex
   digest is rejected (and counted as skipped) server-side.
3. **Minimum match threshold.** The k-anonymity floor, default **50**. When
   the overlap is below the threshold, every quantitative field in the result
   is withheld. You can raise it for stronger privacy; you cannot disable
   suppression — the effective threshold never drops below 1.
4. **Labels.** Optional free-text annotations (*Q3 loyalty list*, *Acme
   Media*) that land on the audit entry so the collaboration is identifiable
   later.

Each side is capped at **50,000 identifiers per request** — chunk larger
lists into multiple matches.

The **result card** reports what the share produced:

* **Meets threshold / below threshold**, with a badge.
* When the threshold is met: the **overlap** count, your **match rate**, the
  partner's match rate, and the **Jaccard similarity** (`|∩| / |∪|`).
* Per-side **identifiers** and **skipped counts** — records dropped before
  hashing because they failed normalization, carried an explicit opt-out, or
  were malformed digests. Skips are counted, never itemized.
* When the threshold is met, the matched intersection digests are flagged as
  **ready to activate** — they are digests you already hold, so mapping them
  back to your own profiles reveals nothing about the partner's
  non-overlapping members.

### How a partner receives the invitation

There is no in-product messaging between the two parties — deliberately. You
and your partner agree the collaboration out of band (email, a data-sharing
agreement, your existing channel). Your partner computes SHA-256 digests of
their identifiers locally, using the published contract above, and hands you
the digest list — never the cleartext. You paste those digests into the tab
(or post them to the API) and run the match. The partner never touches your
Orbit tenant, and the only thing that ever crosses between you is the digest
list and the resulting counts.

## Query and output restrictions — what can run in the room

The room runs exactly one query shape: **set intersection over hashed
identifiers, with aggregate outputs**. There is no general query language in
the room and no path from a match to a partner's rows.

| Runs in the room                                                           | Blocked in the room                                                                              |
| -------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ |
| Overlap **size** (count of shared identifiers) when ≥ threshold            | Row-level results — no list of *who* matched                                                     |
| Per-side **match rates** (overlap ÷ each side's size)                      | Any field of the partner's profiles — the partner's rows never enter the compute                 |
| **Jaccard similarity** of the two sets                                     | Queries over partner data (filters, attribute slices, trait lookups)                             |
| The **intersection digests** — digests you already hold from your own list | A digest you did **not** submit — nothing about non-overlapping partner members is ever returned |
| Chunked matches over ≤ 50,000 identifiers per side                         | Exports, file downloads, or persistence of the partner's set                                     |

Two enforcement points make the blocked column hard, not just policy:

* **Server-side suppression.** Below the effective threshold the API returns
  `meets_threshold: false` with every quantitative field `null` — the numbers
  do not exist in the response, so no client can display them.
* **Intersection-only output.** The result includes only digests present in
  *both* sets. Since you submitted yours, receiving the intersection back
  tells you nothing you could not already compute locally — which is exactly
  what "no data egress" means.

<Note>
  A match result is a measurement, not an export. The matched digests can seed
  an audience activation against **your own** profiles; they are not a copy of
  any partner data, and no example on this page should be read as one.
</Note>

## Operator workflow: create, share, match, close

The room is stateless, so "creating" and "closing" it are operational
boundaries you draw, not objects to provision.

1. **Agree the scope with the partner.** Decide which identifiers both sides
   will hash (email, phone, or both), confirm the partner can produce digests
   per the contract, and set a threshold both sides are comfortable with (the
   default of 50 is a reasonable floor for mid-market audiences).
2. **Open the tab** at Integrations → CDP → Clean room and stage **your**
   audience — export the relevant segment's identifiers, or paste a list
   directly. Annotate the share with your label.
3. **Grant the partner access to the share** by handing them the hashing
   contract and receiving their digest list back. Nothing in your tenant
   changes hands; the digests are the entire invitation.
4. **Run the match.** Paste the partner digests, confirm the threshold, and
   compute. If the result is below threshold, the overlap was too small to
   report safely — match against a larger list or agree a different floor
   with the partner rather than lowering it toward 1, which erodes the
   privacy guarantee for both sides.
5. **Act on the result.** When it meets the threshold, use the overlap count
   and match rates to size the campaign, and the matched digests to assemble
   the activation audience from **your** profiles.
6. **Close the room.** Discarding the pasted inputs closes the collaboration
   — nothing was persisted to remove. The counts-only audit entry stays in
   your audit log as the record that a match ran, with your labels attached.

### End-to-end example: retailer ↔ brand co-marketing

A retailer wants to run a co-marketing campaign with a brand, targeting
customers who buy from both — without the retailer handing over its customer
list and without the brand revealing its loyalty members.

1. The retailer exports its loyalty segment's email identifiers and pastes
   them into **Your audience** — say 12,400 identifiers.
2. The brand hashes its loyalty members locally (same contract: normalized
   email → SHA-256) and sends the retailer a digest list — say 8,900 digests.
   The brand's cleartext never leaves its own systems.
3. The retailer runs the match with the default threshold of 50. The result
   meets the threshold: **overlap 2,140 members**, match rate 17.3% (yours),
   24.0% (partner), Jaccard 0.11.
4. The retailer maps the 2,140 matched digests back to its own profiles and
   builds the co-branded campaign audience. The brand learns the aggregate
   counts — and nothing about which retailer customers did **not** match.
5. Had the overlap come back below 50, the result would have been withheld
   entirely — the campaign idea gets revisited, and no small-sets probe ever
   leaks a single member.

Neither list changed hands. The analytics were shared; the customers were
not.

## Interaction with consent and suppression

The protections you maintain in the
[consent and suppression model](/concepts/consent-and-suppression-model)
travel with your data into every match:

* **Explicit opt-out.** Marking a record `consent: false` (in the API body)
  drops it **before hashing** — it is counted as skipped and can contribute
  nothing to any overlap. A member who withdrew consent can never be matched
  into a collaboration.
* **Skipped, always counted.** Opt-outs, unnormalizable identifiers, and
  malformed partner digests all surface in the per-side skipped counts, so
  you can see suppression working rather than trusting it silently.
* **Revocation between matches.** The room is stateless, so a re-run always
  evaluates your current list. When a contact opts out in the
  [consent model](/concepts/consent-and-suppression-model), remove them from
  the staged list (or exclude them at export) and the next match excludes
  them — there is no stale room holding their identifier.
* **Tenant-owned controls.** As with the send side, consent capture and
  honoring are **your responsibilities**: Orbit carries the mechanism
  (opt-out drop, k-anonymity suppression, hashing-only comparison) and the
  audit trail; it does not make the underlying sharing arrangement lawful for
  you.

## See also

* [Consent and suppression model](/concepts/consent-and-suppression-model) — the recipient-state model whose protections carry into the room
* [CDP identity resolution](/concepts/cdp-identity-resolution) — how identifiers resolve to the profiles a digest maps back to
* [CDP segments](/guides/cdp-segments) — building the audience you stage as your share
* [Audience activation pipeline](/guides/audience-activation-pipeline) — the same hashing contract used to export audiences to destinations
* [Audit log](/guides/audit-log) — where the counts-only match record lands
