Skip to main content

Endpoint

Upsert an end user record and optionally link them to an existing session. Called automatically when you use identify() in the SDK, but you can also call it directly from your backend.
Backfilling or importing existing users? Every identify call bumps the user’s last_seen to now, so looping it over your user base marks everyone “active today” and corrupts activity data. Use Update / Backfill Users instead, which never touches activity timestamps. See Importing & Backfilling Data.

Authentication

Requires your publishable widget key in the Authorization header:
The org_id is derived from the widget key. You do not need to pass it in the request body.

Request Body

string
required
Your system’s unique identifier for this user.
object
User attributes. Recognized fields (name, email, signed_up_at, renewal_date, renewal_status, contract_term, payment_terms, on_contract, mrr, arr) are stored in dedicated columns. All other fields are stored in custom_fields. See User Traits for recommended fields.Do not send id, external_id, org_id, company_id, created_at, updated_at, first_seen, last_seen, or last_contacted_at. These are managed by Halo and the endpoint returns HTTP 400 if any of them appear in the body. Use signed_up_at to set the date the user signed up in your product. See System-managed fields.
object
Structured context entries for AI consumption. Stored in the context jsonb column. Merged with existing context (new keys overwrite). See Context Entries.
string
If provided, any orphaned transcripts or tickets from this session (created before the user was identified) are retroactively linked to the user record.
string
JWT signed with your Identity Secret (HS256). Required when identity verification is enabled for your workspace. The token’s user_id claim must match the user_id field. See Identity Verification.

Recognized Traits

These trait keys are stored in dedicated database columns for filtering and display: All other trait keys are stored as custom fields and are accessible to the AI agent.

Rejected Traits

These keys trigger HTTP 400 if present in the traits body. They are managed by Halo and customer-set values would silently shadow the real columns. See System-managed fields for context.

Example

Response:
All timestamps in the response are Postgres timestamptz values serialized as ISO 8601 with microsecond precision and a +00:00 offset.
  • created_at, updated_at, first_seen, last_seen, and last_contacted_at are read-only Halo-managed timestamps. Do not echo them back into the traits body on subsequent calls (the endpoint returns HTTP 400 if you do).
  • signed_up_at is customer-writable via traits.signed_up_at on a follow-up identify call. On update the column uses COALESCE semantics, so passing null (or omitting the key) preserves the existing value. Pass a new ISO 8601 string to overwrite.

Behavior

  • If a user with the same user_id exists within your organization, it is updated
  • If it doesn’t exist, it is created
  • Traits are merged: existing values are preserved, matching keys are overwritten
  • Context is merged: existing keys are preserved, new keys are added, matching keys are overwritten
  • The combined size of traits and context must not exceed 20,000 bytes
  • When session_id is provided, orphaned transcripts and tickets from that session are retroactively linked to the user