Skip to Content
📚 Documentation is in active development — pages marked TODO will fill out over the next few weeks.
ContactsOverview

Contacts

A Contact Profile represents one human. Multiple identities (WhatsApp wa_id, Telegram chat_id, email, web visitor token, …) can all point to the same profile — so a customer who switches from WhatsApp to email is recognised as the same person and conversations stay linked to one profile.

Data model

ContactProfile (one row per human) ├── ContactIdentity[] ← channel-specific identifiers ├── attributes (JSONB) ← reserved + custom fields ├── tags[] ← labels ├── accountId ← optional B2B link └── source ← inbound:whatsapp | csv_import | manual | api | …

How contacts get created

  • Inbound message — first message on a new channel creates a Profile + Identity
  • CSV import — bulk upload from /dashboard/contacts → Import
  • Manual entry/dashboard/contacts → New contact
  • Public booking — booking creates a Profile if the phone isn’t already known
  • API/api/public/... endpoints can find-or-create by phone

Cross-channel auto-merge

When a new identity is added, the platform checks for matching primaryPhone or primaryEmail on existing profiles. A match merges (the new identity is attached to the existing profile) instead of creating a duplicate. Phone-like identities (phone, whatsapp_wa_id) merge with each other; emails merge with each other; the rest don’t auto-merge (manual merge UI for now).

Reserved vs custom fields

  • Reserved fields are platform-defined: first_name, last_name, phone, email, dob, etc. Always present in suggestions, type-validated.
  • Custom fields are agency-defined per-scope (CONTACT, ACCOUNT, AGENCY). Used for things like lead_score, class_grade, preferred_pickup_time.

Custom fields

Last updated on