Skip to main content

Overview

Company traits describe the organization a user belongs to. They’re set via identifyCompany() or in the companyTraits config option, stored in the companies table, and included in the AI prompt as [Company: Name]. Fields marked with column are stored in dedicated database columns; all others go into custom_fields.
FieldTypeStorageDescription
namestringcolumnCompany display name
domainstringcolumnCompany’s website domain
industrystringcolumnIndustry vertical
planstringcolumnSubscription plan
employee_countnumbercolumnNumber of employees
monthly_spendnumbercustom_fieldsMonthly spend or MRR
signed_up_atstringcolumnISO 8601 date the company onboarded in your product. Optional, but strongly recommended for backfills, integration syncs, and any flow where Halo first sees the company well after the real customer relationship started. Populates the dedicated companies.signed_up_at column so it powers the “Customer since” view in the dashboard, segments, automations targeting customer tenure, broadcasts, the AI prompt, and the {{ company.signed_up_at }} email merge field. Same field name and semantics on userTraits.
renewal_statusstringcolumnRenewal status. Allowed: up_for_renewal, in_progress, likely_to_renew, expansion_opportunity, set_to_cancel, set_to_cancel_trial, at_risk, renewed, lost. Auto-derived or manual.
renewal_datestringcolumnNext renewal date (ISO 8601). Auto-derived from Stripe/PandaDoc or manual.
contract_termstringcolumnAgreement frequency: monthly, quarterly, annual, bi_annual
payment_termsstringcolumnBilling frequency: monthly, quarterly, annual, bi_annual
on_contractbooleancolumnWhether the company has an active contract
Do not send created_at, updated_at, id, health_score, team_size, or last_contacted_at in companyTraits. Halo manages these. The SDK rejects requests that include any of them with HTTP 400. If you want to set the date the company onboarded in your product, use signed_up_at (above). See System-managed fields for the full list.

System-managed fields

Halo sets these automatically. Do not pass them in companyTraits or in the REST traits body. The SDK returns HTTP 400 on any of these keys.
FieldTypeDescription
idstring (uuid)Halo’s internal primary key. Use company_id (your external ID) to upsert.
created_atstringISO 8601 timestamp set when Halo first inserted the row. Distinct from signed_up_at (customer-set onboarding date).
updated_atstringISO 8601 timestamp of the most recent write to the row. Maintained by upsert_company and a database trigger.
health_scorenumberAccount health score (0 to 100). See Health Scores.
team_sizenumberNumber of end users linked to this company. Computed at chat time.
last_contacted_atstringISO 8601 timestamp of the last time any user at this company was contacted (broadcast, series, ticket reply, or chat). Updated automatically.
signed_up_at vs created_at: signed_up_at is yours. Customer-set, optional, treated as a historical timestamp. Pass it to record when the company onboarded in your product. created_at is Halo’s. It tracks when the row was first inserted in our database, which is often days, months, or years later than the actual customer signup (especially for companies backfilled from Intercom, HubSpot, Stripe, or CSV import). The dashboard surfaces both as “Customer since” and “Added to Halo” respectively.

Do I need to send signed_up_at?

If you call identifyCompany() synchronously the moment a company is created in your product, sending it is optional. created_at will be approximately equal to the real signup time. You should send it whenever any of these are true:
  • You’re backfilling existing companies via the API or CSV import
  • You first identify a company via an integration sync (HubSpot, Stripe, Intercom). The Halo integrations auto-populate signed_up_at from the upstream system precisely so dashboards and automations don’t treat every imported company as new.
  • You want segments and automations targeting customer tenure (“Companies that became customers in Q3 2024”, “Renewal reminder 11 months after signup”) to measure from the real start date.

How automations and segments read signed_up_at

  • The series “User signs up” trigger (entry_trigger: "user_created") fires on end_users row creation, not on company signed_up_at. It’s not the right tool for “company onboarded” automations. Use an event trigger or a segment_match trigger keyed on company.signed_up_at instead.
  • Audience filter on company.signed_up_at reads the column directly. Companies with no signed_up_at are skipped by that filter. There is no fallback. Send signed_up_at if you want segments based on company tenure to be accurate.
  • days_since_signup in onboarding rules and Slack mode-determination prefers signed_up_at, falling back to created_at when missing. So that signal degrades gracefully.
  • {{ company.signed_up_at }} email merge field renders blank when the column is null. Use {{ company.signed_up_at|recently }} if you mix backfilled and SDK-only companies.

Custom fields

Add any field you need:
ha.identifyCompany("company_456", {
  name: "Acme Corp",
  plan: "enterprise",
  industry: "SaaS",
  employee_count: 150,

  renewal_date: "2026-06-15",
  contract_term: "annual",
  payment_terms: "monthly",
  on_contract: true,
  renewal_status: "up_for_renewal",

  space_id: "space_789",
  region: "us-east",
  account_manager: "Sarah Johnson",
  total_ad_spend: 50000,
  active_campaigns: 12,
});

TypeScript interface

interface CompanyTraits {
  name?: string;
  domain?: string;
  industry?: string;
  plan?: string;
  employee_count?: number;
  monthly_spend?: number;
  signed_up_at?: string;
  renewal_status?: string;
  renewal_date?: string;
  contract_term?: string;
  payment_terms?: string;
  on_contract?: boolean;
  [key: string]: unknown;
}
The [key: string]: unknown index signature lets you pass any custom field, but the reserved keys in the System-managed fields table above (id, created_at, updated_at, health_score, team_size, last_contacted_at) are not writable. Sending any of them returns HTTP 400 from /api/sdk/companies/identify.

User-company linking

When you call identifyCompany(), the current user is automatically linked to that company via end_users.company_id -> companies.id. This means:
  • Every chat message includes both user and company traits/context
  • You only need to call identifyCompany() once per user. The link persists
  • Multiple users at the same company share company-scoped data

AI prompt output

For a company with the traits above, the AI sees:
[Company: Acme Corp]
Plan: enterprise
Industry: SaaS
Employee count: 150
Space id: space_789
Region: us-east
Account manager: Sarah Johnson
Contract renewal: 2026-06-15
Total ad spend: 50000
Active campaigns: 12
The formatter automatically converts snake_case keys to readable labels.

Where to go next

Context Entries

Structured state data beyond simple traits.

Examples

Real-world integration patterns.