Skip to main content

The contact record model

Every message you send, every campaign and flow you build, every inbox conversation, and every segment you define resolves to a contact: one row per person in your tenant, holding who they are, where they can be reached, and what they consented to. This page defines that row. The surrounding pages each cover one mechanism around it; this one covers the record itself.

What a contact is

A contact is the canonical identity anchor for a person inside one tenant. Other pillars reference it by id and never copy it:
  • A campaign or batch send selects contacts (directly or through a segment) and resolves a reachable address per channel at send time.
  • A flow branches on contact fields and writes back to them.
  • An inbox conversation is attached to a contact so an agent sees the person’s history, not just the thread.
  • The CDP layers traits, scores, and segments on top of the record.
  • Billing meters outcome usage against the tenant, attributed per contact touch.
The record itself holds scalar profile fields (first_name, last_name, display_name, company, country_code, timezone, language), the channel addresses in the next section, your external_id for the person, tags, and any custom fields you defined. It deliberately does not hold message history, call records, or event streams; those live on their own surfaces and join back to the contact id (see Timeline and journey). How this page relates to the fragments around the record: Read this page first if you are new to Orbit: it defines the noun the other pages operate on.

Identifiers

Every contact carries one immutable system id, assigned at creation: cnt_ followed by 32 lowercase hex characters, for example cnt_4f9a2c81d03b4e6a7f1c2b3d4e5f6071. The id is never reassigned and never reused: when two records merge, the survivor keeps its id; when a record is erased, the id is tombstoned. External identifiers index the record from the outside: Uniqueness is per tenant and per identifier type. Within a tenant, a normalized email or phone identifies at most one live contact for matching purposes: an import that arrives carrying an email or phone an existing contact already holds resolves to that contact instead of creating a second row, and the server-side duplicate scanner groups on normalized email and phone (see Duplicates). external_id is the key we recommend your integrations store: unlike an address, it never changes when the person changes handset or inbox, so it keeps resolving after merges and re-imports.

Channel addresses

One contact holds several reachable addresses at once: an SMS-capable phone number, a WhatsApp identity, an email address, a Viber identity, and push tokens. A send on a given channel resolves to that channel’s address on the record; when more than one candidate exists, the contact’s channel preferences and your messaging services decide which address is used (see sender resolution and message send request lifecycle). Consent attaches per channel address, not to the record as a whole. A contact who opted in on SMS and never on email is sendable on one channel and suppressed on the other. The consent state, the quiet-hours and frequency-cap checks, and the suppression lists that enforce it are defined in consent and suppression model; this page only fixes where consent lives: on the channel address of a specific contact, never implied by the contact existing.

Lifecycle

A contact moves through four states:
  1. Created. Creation paths that write a contact row:
    • POST /api/v1/contacts from your backend or an SDK.
    • CSV and CRM imports, which upsert: a row whose identifier matches an existing contact is updated, not duplicated (see imports migration model).
    • Inbound auto-create: a message or call from an address your tenant has never seen resolves to a new contact so the conversation has a person attached (see inbound message resolution).
    • Form and widget captures, and CRM sync connectors.
  2. Active. The normal state. Profile fields, tags, custom fields, and addresses are editable from the dashboard, the API, imports, and enrichment.
  3. Merged-into. A duplicate is folded into a survivor. The secondary id stops being a live record and is kept as an alias that resolves to the survivor, so stored references keep working (see Duplicates).
  4. Erased. A GDPR erasure tombstones the row (see Erasure and retention).
Who may act is scoped like every other tenant resource: dashboard roles gate create, edit, merge, and erase in the console, and API keys carry the scopes your integration was granted. The role and scope matrix is roles, teams, and permissions.

Duplicates: two collapse mechanisms

Two different mechanisms remove duplicate records, and they fire at different points:
  • Deterministic merge collapses one pair (up to 20 secondaries per request) into one survivor, per-field, with an audit row and a 30-minute undo window. You drive it: the duplicate scanner proposes groups on normalized email and phone, and you or your code post the merge. This is contact merge policy.
  • Probabilistic identity resolution proposes likely-matches at scale from the CDP side, feeding a review queue instead of merging directly. This is CDP identity resolution, backed by the identity and device graphs.
Use deterministic merge when you have a concrete pair you know is one person; use identity resolution when you are scanning a whole audience for records that might be the same person. Both write to the same contact rows defined on this page.

Enrichment

Enrichment is an overlay on the record, not a second record. The enrichment surface derives traits a sender did not provide (for example a timezone resolved from a phone number) and next-best-action fields used by decisioning, and writes them onto the contact so every reader sees them. Two rules keep the overlay honest:
  • Enrichment does not silently overwrite values an operator or your API set by hand; manual edits keep precedence and carry their own history.
  • Enriched traits are readable and filterable exactly like imported fields, so segments and personalization slots do not need to know where a value came from.
The inbound side of the same idea (deriving intent and traits from an incoming message) is covered in inbound AI enrichment.

Timeline and journey

The per-contact timeline assembles the events that reference the contact id into one ordered feed: sent and received messages, calls, campaign touches, consent changes, and operator notes. The journey view is the campaign- and flow-shaped cut of the same events: which sends reached this person, with what outcome. Neither is stored as a copy on the contact row; both are read-time assemblies over the event surfaces, which is why a message deleted or retired by a retention window disappears from the timeline too. The Customer 360 snapshot is the third read over the same record: it composes the timeline, consent state, scores, and open conversations into the drawer an agent sees. Timeline and 360 differ only in composition, not in source data.

Bulk operations and limits

For more than a handful of records, use the bulk surface instead of looping single-record calls:
  • Bulk tag, field update, and lifecycle-stage changes accept up to 50,000 contact ids per request.
  • Tag writes carry at most 25 tags per contact, each tag up to 50 characters; name fields cap at 200 characters and timezone at 64.
  • Bulk operations are idempotent by construction where it matters: re-applying the same stage change to a contact already in that stage is a no-op, so a retry after a timeout does not double-apply.
  • CSV-scale ingestion runs as import jobs with their own history and matching rules rather than as one giant request (see imports migration model).
The full request and response payloads, including the bulk and import endpoints, are in the contacts API reference.

Erasure and retention

Erasure is the terminal state and is treated as a legal operation, not a delete button: the request requires re-authentication, the profile fields and channel addresses are scrubbed, references in histories are anonymized, and the id is tombstoned rather than recycled. Two things deliberately survive:
  • Suppression entries. The person’s addresses are projected onto the suppression surface so an erased contact can never be re-imported into a send. Suppression outliving the record is what makes the erasure durable (see consent and suppression model).
  • Held records. Rows the law keeps independent of the contact, such as billing and consent audit trails, persist in their own surfaces under their own retention rules (see retention windows and deletion).
The end-to-end data-subject workflow, including what a DSAR export contains after a merge history exists, is DSAR concept.

Worked example: one contact, four surfaces

  1. Inbound SMS creates the record. A text arrives from +14155550100, an address your tenant has never seen. Inbound resolution creates a contact with that phone address, assigns a cnt_ id, and attaches the conversation. Surface: messaging inbound plus the contacts record.
  2. Enrichment layers on. The enrichment surface derives a timezone from the number and writes next-best-action fields onto the same row. Surface: enrichment overlay; the record gains traits without a second row appearing anywhere.
  3. A CRM import lands a duplicate, and merge collapses it. Your nightly import carries the same person as external_id: c-8841 with an email address. Import matching keeps both rows because no identifier overlapped; the duplicate scanner or identity resolution later proposes the pair; you post POST /api/v1/contacts/merge with the imported record as secondary. The survivor keeps its cnt_ id, unions the email and external_id onto the record, folds the import’s conversation history in, and writes an audit row. Surface: imports, then the deterministic merge.
  4. The person asks to be erased. You issue the erasure with re-authentication. The profile and addresses are scrubbed, the id is tombstoned, the phone and email are projected onto suppression, and the merge audit remains available for disclosure. Surface: erasure plus suppression; the timeline now shows an anonymized history.
At no point did any pillar copy the person’s data: every surface either wrote to the one record or joined to its id.

See also