Who owns which field, and when.
Three record types, several write paths, one predictable rule set. This page is the reference for what happens when product signals and human edits meet.
Objects
- Visitor — a browser session. Holds transient behavior: page views, chats, tracked events, custom traits from
set(). - Contact — a person. Holds identity: email, name, tags, notes, links to companies.
- Company — an organization. Holds the deal: stage, deal value, domain, industry, notes, health signals.
Visitors link to contacts; contacts link to companies (many-to-many — a consultant can belong to three clients).
Where data belongs
| Data | Lives on | Why |
|---|---|---|
| Plan, seats, usage traits | Visitor / Contact traits | Product truth, updated by identify/set |
| Stage, deal value | Company | Sales truth, negotiated by humans |
| Tags, notes | Contact / Company | Team knowledge |
| Page views, events | Visitor | Session behavior |
Write semantics during identify
When identify() or the server identify route fires:
- Match visitor → contact by
user_id, thenemail; create if neither matches. - Merge provided traits into the contact (flat scalars, merge semantics).
- If a company is provided, match by domain first, then name; create and link if new.
- If a stage is provided, apply stage precedence (below).
Stage precedence
Stages have an order. Writes resolve by source:
- Manual (dashboard drag) — always wins, exactly as placed.
- Product signal (widget/API, advance-only default) — applies only if the target stage is later in your order than the current one. A churned-user signal can never demote a won deal.
- Exact mode —
setStage(stage, "exact")orstage_update_mode: "exact"on identify applies the stage unconditionally. Use for intentional backward moves.
Deal value
Deal value lives on the company. Writes from setDeal(), the API, and the dashboard all set the same field — last write wins, so decide which system is authoritative for value (usually billing or the dashboard, not the widget).
Custom metadata
Contacts and companies accept a flat metadata object via the public API, MCP, and dashboard: strings, numbers, booleans, or nulls only; up to 50 keys; string values ≤ 1024 chars; total ≤ 10KB. API writes replace the whole object; widget set() merges. Invalid payloads get a 400 with a specific message.