API details

Visitor and conversation endpoints sit underneath the widget and inbox workflow.

Use these endpoints when you need lower-level customer messaging integration details, especially for identity and CRM context sync tied to a widget visitor.

Typical uses

  • Create or update visitor identity and profile data
  • Push custom contact metadata from your product into HAL
  • Track visitor or company milestones from your product using simple event names
  • Link a visitor to one or more CRM companies
  • Update company stage and company metadata during identify

The route most customers want

If you want your own app or backend to send custom contact or company information into HAL, use POST /api/widget/identify.

POST https://api.chatwithhal.com/api/widget/identify

This route is the supported integration surface for enriching a widget visitor with known customer identity and CRM context.

Request fields

  • app_id or project_id: the HAL project
  • visitor_id: your stable identifier for the current visitor session or user
  • user_id, email, name: known identity fields
  • metadata: custom contact-level properties to merge into HAL contact context
  • company_ids: one or more existing HAL company IDs to link
  • company: optional company object with id, name, stage, and metadata
  • stage_update_mode: optional override for CRM stage precedence
  • user_hash: optional verification signature when identity verification is enabled

What HAL does with the data

  • metadata is merged into the linked contact metadata when the visitor can be mapped to a contact
  • company.metadata is merged into the linked company metadata
  • company.stage can move the linked company in CRM, subject to stage precedence rules
  • company.id sets the active company context for later widget company writes
  • company_ids links the visitor’s contact to existing HAL companies

Write semantics

  • If the visitor has an email, identify can create or match the contact automatically
  • If that contact has no linked companies yet, HAL can create a placeholder company so the record has account context
  • If you later send a real company ID, HAL links the contact to that company and may remove the placeholder if nothing else uses it
  • company_ids links existing companies; it does not create new companies by arbitrary ID
  • company.id is the recommended way to establish which linked company later widget CRM writes should target by default
  • company.metadata and contact metadata are merged, not replaced wholesale

Example request

{
  "app_id": "proj_123",
  "visitor_id": "user_456",
  "email": "alice@acme.com",
  "name": "Alice",
  "metadata": {
    "plan": "enterprise",
    "role": "admin"
  },
  "company": {
    "id": "acme",
    "name": "Acme Inc",
    "stage": "Qualified",
    "metadata": {
      "arr_band": "50k-100k",
      "crm_owner": "Sam"
    }
  }
}

Use set-properties for follow-up updates

If you already identified the visitor and only need to append or refresh custom visitor/contact properties, use POST /api/widget/set-properties.

{
  "app_id": "proj_123",
  "visitor_id": "user_456",
  "properties": {
    "feature_flag": "beta",
    "last_seen_area": "billing"
  }
}

This route merges into visitor and linked contact metadata. It is useful for product context, but it does not update company metadata.

Track milestone events

Use POST /api/widget/track when you want to record that a visitor or linked company did something at a point in time. Keep this simple: send a high-level event name and an optional scalar value.

{
  "app_id": "proj_123",
  "visitor_id": "user_456",
  "name": "plan-upgraded",
  "value": "pro",
  "entity": "visitor"
}

For company-level events, set entity to company. The visitor must already resolve to a contact with a linked company. Pass company_id when you need to target a specific linked company.

{
  "app_id": "proj_123",
  "visitor_id": "user_456",
  "name": "deal-risk-changed",
  "value": "high",
  "entity": "company",
  "company_id": "acme"
}
  • name is required, lowercased on the server, and . / $ are normalized to -
  • value is optional and must be a string, number, boolean, or null
  • Use events for milestones and product signals, not raw clickstream noise
  • These events surface in contact timelines, company activity, and inbox context

Set stage for an identified visitor

Use POST /api/widget/set-stage when a widget-side session should move the active or explicitly targeted company to a specific CRM stage.

{
  "app_id": "proj_123",
  "visitor_id": "user_456",
  "company_id": "acme",
  "stage_name": "Qualified",
  "stage_update_mode": "advance_only"
}

This requires the visitor to already resolve to a contact with at least one linked company. In practice, that usually means calling identify() with an email first and sending company.id when you know the current account.

Set deal value for an identified visitor

Use POST /api/widget/set-deal when your app knows the current opportunity value and should update the active or explicitly targeted company record.

{
  "app_id": "proj_123",
  "visitor_id": "user_456",
  "company_id": "acme",
  "deal_value": 12000,
  "stage_name": "Qualified",
  "notes": "Annual plan in security review"
}
  • deal_value writes to the company, not the contact
  • stage_name is optional
  • company_id is optional but recommended when the contact can be linked to multiple companies
  • The route fails if the visitor has not been identified into a contact/company context yet

Company selection order

  • If company_id is provided on track, set-stage, or set-deal, HAL targets that linked company
  • Otherwise HAL uses the active company established by identify(company.id)
  • If there is no active company but exactly one linked company exists, HAL uses that company
  • A legacy first-linked fallback still exists for backward compatibility, but new integrations should not rely on link order

CRM stage precedence

Widget-facing stage updates accept an optional stage_update_mode field on visitor identify and stage-setting requests.

  • advance_only is the default and prevents widget updates from moving a company backward when the CRM already has a later stage
  • exact applies the requested stage exactly and should be reserved for authoritative backend syncs

The project default can be configured in Settings → Widget → CRM Stage Sync. Per-request values still override that default when you need different behavior.

What this is not

The current public API key surface under /api/v1 does not yet expose visitor/company metadata write routes. Use the widget-facing identify flow when you need to push end-user contact and company context.

Likewise, tracked events are widget-facing in v1. They are intentionally simpler than a full analytics event schema: just a name plus an optional scalar value.

Common failure cases

  • 401 on identify when identity verification is required but user_hash is missing or invalid
  • 404 on set-stage or set-deal when the supplied visitor_id does not exist in the project
  • 400 on set-stage or set-deal when the visitor exists but is not linked to a contact/company yet
  • 400 company_not_linked on track, set-stage, or set-deal when an explicit company_id is supplied but is not linked to the visitor's contact
  • 400 on track when entity is company but the visitor has no linked company context
  • 400 on track when value is not a scalar or name is blank
  • 400 when stage_update_mode is not one of advance_only or exact

Guidance

Prefer the higher-level REST API and MCP docs first. Drop to this layer when you need deeper visitor-specific integration behavior tied to a customer-facing widget session.