Product · Sales

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

DataLives onWhy
Plan, seats, usage traitsVisitor / Contact traitsProduct truth, updated by identify/set
Stage, deal valueCompanySales truth, negotiated by humans
Tags, notesContact / CompanyTeam knowledge
Page views, eventsVisitorSession behavior

Write semantics during identify

When identify() or the server identify route fires:

  1. Match visitor → contact by user_id, then email; create if neither matches.
  2. Merge provided traits into the contact (flat scalars, merge semantics).
  3. If a company is provided, match by domain first, then name; create and link if new.
  4. 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 modesetStage(stage, "exact") or stage_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.