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/identifyThis route is the supported integration surface for enriching a widget visitor with known customer identity and CRM context.
Request fields
app_idorproject_id: the HAL projectvisitor_id: your stable identifier for the current visitor session or useruser_id,email,name: known identity fieldsmetadata: custom contact-level properties to merge into HAL contact contextcompany_ids: one or more existing HAL company IDs to linkcompany: optional company object withid,name,stage, andmetadatastage_update_mode: optional override for CRM stage precedenceuser_hash: optional verification signature when identity verification is enabled
What HAL does with the data
metadatais merged into the linked contact metadata when the visitor can be mapped to a contactcompany.metadatais merged into the linked company metadatacompany.stagecan move the linked company in CRM, subject to stage precedence rulescompany.idsets the active company context for later widget company writescompany_idslinks 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_idslinks existing companies; it does not create new companies by arbitrary IDcompany.idis the recommended way to establish which linked company later widget CRM writes should target by defaultcompany.metadataand contactmetadataare 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"
}nameis required, lowercased on the server, and./$are normalized to-valueis optional and must be a string, number, boolean, ornull- 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_valuewrites to the company, not the contactstage_nameis optionalcompany_idis 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_idis provided ontrack,set-stage, orset-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_onlyis the default and prevents widget updates from moving a company backward when the CRM already has a later stageexactapplies 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
401on identify when identity verification is required butuser_hashis missing or invalid404on set-stage or set-deal when the suppliedvisitor_iddoes not exist in the project400on set-stage or set-deal when the visitor exists but is not linked to a contact/company yet400 company_not_linkedon track, set-stage, or set-deal when an explicitcompany_idis supplied but is not linked to the visitor's contact400on track whenentityiscompanybut the visitor has no linked company context400on track whenvalueis not a scalar ornameis blank400whenstage_update_modeis not one ofadvance_onlyorexact
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.