Contacts
One row per person you can message. Contacts are tenant-scoped and identified by phone number, so they accumulate automatically whether they arrive via form, import, or inbound chat.
The contact model
phoneNumberrequiredE.164 string. Unique per workspace — used as the natural key for find-or-create.firstName / lastNameoptionalStrings, used by template variable substitution and {{fullName}}.emailoptionalUsed for the {{email}} variable and the contact detail panel.optedInboolDefault true. Required by Meta — a contact can only be messaged after opt-in is recorded.optedInAtdatetimeSet when optedIn flips to true. Auditable proof of consent.optOutboolDefault false. When true, the contact is suppressed from campaigns and broadcasts.dobdateOptional date-of-birth, used by the CONTACT_BIRTHDAY automation trigger.customFieldsjsonFree-form JSON for attributes that aren’t on the model (loyalty tier, last visit, etc.).
Phone is the natural key
Each phone number is unique within your workspace. Every write path — manual, import, form, inbound, API — runs find-or-create against (tenantId, phoneNumber). Repeat submissions update the existing row instead of duplicating it.
Creating contacts
Manual entry
Add one contact from the Contacts page. Validates phone format and dedupes by phone.
CSV import
Bulk upload with a per-tenant advisory lock and ImportJob tracking. New rows fire CONTACT_CREATED; duplicates are skipped.
Lead form
When a hosted form is submitted, the form payload is upserted into a contact by phone.
Inbound message
If a contact messages your WhatsApp number and no row exists, one is created automatically.
REST API
POST /api/v1/contacts find-or-creates by phoneNumber. 200 on match, 201 on create.
Tags
Tags are flat labels — a many-to-many between contacts and a per-tenant tag list. They are the only primitive automations and segments can match on, so naming matters: keep the names stable.
Working with tags
- Apply via the Contacts UI, the contact
PATCH /api/v1/contacts/:idwithtagIds, or anADD_TAGautomation step. - Tag names are unique per tenant. The
CONTACT_TAG_ADDEDautomation trigger fires only when tags are genuinely added (id diff), so re-applying the same tag is a no-op.
Segments
A segment is a saved filter — JSON evaluated against your contact list. The most common filter is "contacts in tags X, Y". Note that segments match tag names, not tag IDs, so renaming a tag silently breaks any segment that depended on it.
No native Meta groups
WhatsApp Cloud API has no group primitive — segments are a WhautX layer that compiles to a contact list at send time. Audience size on Meta’s side is irrelevant; trust the WhautX segment count.
Custom fields
Anything that isn’t a model column lives in customFields as JSON. Use them for loyalty tiers, last-visit dates, internal IDs you need to look up by, etc. An UPDATE_CONTACT automation step writes non-whitelisted fields there automatically.