Technical docs
Contacts and Channels
Public reference generated from tech docs/contacts-and-channels.md.
Overview
Support reference for how customer identity is linked across Omni, public webchat, API threads, and internal chat.
Core model
- `Contact` is the stable person record. - `ContactChannel` is the per-channel identity for that contact. - `Conversation` always attaches to a `contact_channel_id`, not directly to `contact_id`.
Stored channel types
- The `ContactChannel` model currently defines: - `api` - `web_chat` - `skill_chat` - `employee_chat` - Frontend Omni types still mention `test_chat`, but the backend channel enum does not.
Identity key and profile fields
- Contact-channel uniqueness is enforced by: - `organization_id + channel + external_id` - Key `ContactChannel` fields used in current code: - `contact_channel_id` - `contact_id` - `channel` - `external_id` - `auto_name` - `first_name` - `last_name` - `phone` - `email` - `current_conversation_id` - `auto_name` is generated when a channel record is first created. - Phone and email are normalized on create/update.
Omni customer mapping
- Omni customer identity is derived from the backend `contact_channel` payload on the conversation. - Display name resolution is: - `first_name + last_name` - otherwise `auto_name` - otherwise `Unknown Customer` - The current Omni customer summary uses `contact_channel` data for: - name - email - phone
Public webchat linkage
- Public webchat session identity is resolved in this order: - valid `webchat-token` - `contact.external_id` from the request - generated UUID - Once a public message flow resolves an external ID, `UpsertWebchatConversation` upserts a `web_chat` contact channel for: - organization - channel = `web_chat` - that external ID - Re-sending the same `contact.external_id` reuses the same `ContactChannel`. - Supplying profile fields (`first_name`, `last_name`, `phone`, `email`) updates the existing `ContactChannel` instead of creating a second one.
Conversation linkage and reuse
- All conversations belong to one `contact_channel_id`. - For public webchat: - default behavior is single-conversation reuse per resolved `web_chat` contact channel and AI employee scope - if `multi_conversations` is true: - explicit `conversation_id` reuses that conversation - no `conversation_id` creates a new conversation - `GET /api/public/webchat/conversations` lists all conversations for a resolved `web_chat` contact channel.
`current_conversation_id` behavior
- `current_conversation_id` exists on `ContactChannel`. - In the current codebase it is actively used and updated for internal chat conversation resolution. - Internal chat resolution order is: - explicit `create_new_conversation` - explicit `conversation_id` - `current_conversation_id` - latest conversation for that contact channel - Public webchat upsert does not currently use or update `current_conversation_id`. - Omni queue loading also does not rely on `current_conversation_id`.
Internal chat external IDs
- Internal chat still uses the same `Contact` and `ContactChannel` system. - Internal chat uses generated external IDs for skill and AI teammate chat channels.
- There is no separate stored `ally_chat` contact-channel type in the current backend model. - The skill-chat prefix is still `test__` in `ContactService`, even though the stored channel type is `skill_chat`.
Search behavior
- `ConversationService.searchByContactChannel(...)` is scoped to one contact channel at a time. - Search sources are: - conversation title via `ILIKE` - message content via full-text search - Title hits are returned before message hits. - Search can also be scoped by `aiEmployeeId`.
Support checks
- If a public widget user unexpectedly forks into a second identity: - inspect `channel + external_id` - inspect whether the widget changed `contact.external_id` - inspect whether the stored `webchat-token` belongs to a different external ID - If an internal chat thread reopens the wrong conversation: - inspect `current_conversation_id` - inspect `contact_channel_id` - inspect the generated external ID format for that channel - for Ally, confirm which per-user Ally employee was resolved - If Omni shows the wrong customer name: - inspect `first_name` - inspect `last_name` - inspect `auto_name`