CRM data

HAL CRM data is structured to keep pipeline context useful to the operator.

Companies, contacts, stages, and notes should support action, not just record-keeping.

Key objects

  • Visitor: widget-side identity keyed by your external visitor_id; can later be linked to a contact and company records
  • Contact: person identity with name, email, phone, notes, tags, metadata, and linked visitors
  • Company: account-level record with stage, deal value, notes, metadata, and linked contacts
  • CRM stage: project-scoped pipeline stage used for ordering and stage precedence decisions

Where data belongs

  • Company: deal value, pipeline stage, domain, industry, enrichment fields, and account-level notes
  • Contact: person identity, tags, notes, and contact-specific metadata such as role, plan, or owner
  • Visitor: browser-session identity, widget-facing properties, and transient product context attached before or during chat

Where deal value lives

Deal value is stored on the company record, not the individual contact. This keeps opportunity context attached to the account even when multiple people from the same company talk to HAL.

  • Widget sync: window.HalWidget.setDeal({ deal_value, company_id }) updates the active or explicitly targeted company after the visitor is identified
  • Account API: direct company create and update routes also accept deal_value

Linking rules

  • identify() is the main path that turns a widget visitor into a richer CRM record
  • When identify includes an email or user ID, HAL can match or create the related contact
  • company.id or company_ids can link the contact to one or more existing companies
  • company.id is also the recommended way to establish the active company context for later widget CRM writes
  • If you send a company object, HAL can also merge company metadata and optionally move the company stage

Many-to-many model

Contacts and companies are linked through a junction model rather than a single foreign key.

  • One contact can be linked to multiple companies
  • One company can be linked to multiple contacts
  • The dashboard often shows a primary company view for convenience, but the underlying model is many-to-many
  • Widget-side writes such as setStage() and setDeal() use explicit company_id first, then the active company from identify(company.id), then a single linked company

Write semantics during identify

  • If the visitor already has a linked contact, HAL reuses it instead of creating another contact
  • If no matching contact exists for the email, HAL creates one and links the visitor to it
  • If the contact has no companies yet, HAL creates or reuses a placeholder company so company-level CRM fields have somewhere to live
  • If a real company ID is later linked, HAL can unlink and delete the placeholder when no other contacts depend on it
  • If company metadata is provided, it is merged into the chosen primary company record
  • If contact metadata is provided, it is merged into the linked contact record

Stage precedence

Widget-originated stage writes support two modes:

  • advance_only: default; prevents widget sync from moving a company backward if CRM already shows a later stage
  • exact: applies the requested stage exactly and is best reserved for authoritative backend syncs

Manual route semantics

  • Manual contact create and update routes write contact fields only
  • Manual company create and update routes write company fields only
  • Linking and unlinking contacts to companies is a separate operation on top of those base records
  • Deleting a contact unlinks visitors and removes its company junction records, but does not delete the companies themselves
  • Deleting a company removes its contact junction records, but does not delete the contacts themselves

Common sync pattern

await window.HalWidget.identify({
  visitor_id: 'user_456',
  email: 'alice@acme.com',
  metadata: { plan: 'enterprise', role: 'admin' },
  company: {
    id: 'acme',
    name: 'Acme Inc',
    stage: 'Qualified',
    metadata: { crm_owner: 'Sam' }
  }
});

await window.HalWidget.setDeal({
  company_id: 'acme',
  deal_value: 12000,
  stage_name: 'Qualified'
});

How the operator uses it

CRM data becomes one of the inputs that can move the current focus lane toward sales when the pipeline deserves immediate attention.