Conversation Lifecycle

How conversations flow from creation to resolution — status transitions, auto-assignment, internal notes, and ticket conversion.

Overview

A conversation in ZapTicket represents a real-time chat thread between a visitor and your support team. Every conversation follows a clear lifecycle from creation through resolution.

Status Flow

Conversation States
┌──────────────────────────────────────────────────────────┐
│                                                          │
│   Open ──────────────► Assigned ──────────────► Closed   │
│     │                     │                       │      │
│     │     (auto-assign    │                       │      │
│     │      on first       │                       │      │
│     │      agent reply)   │                       │      │
│     │                     │                       │      │
│     └─────────────────────┼───────── Reopen ◄─────┘      │
│                           │                              │
└──────────────────────────────────────────────────────────┘

Open

Initial state when a visitor starts a conversation. No agent has been assigned yet. The conversation appears in the shared inbox for all agents to see.

Assigned

An agent has taken ownership. This happens in two ways:

  • Auto-assign on first reply — When an agent sends their first message in an open conversation, they're automatically assigned
  • Manual assignment — An agent or admin explicitly assigns via the PATCH endpoint or dashboard UI

Closed

The conversation is resolved. The visitor's widget shows a "conversation ended" state. The agent can close conversations manually, or they can be closed programmatically via the API.

💡Closed conversations can be reopened by changing their status back to "open". If the visitor sends a new message in a closed conversation, it's automatically reopened.

Auto-Assignment

When an agent sends their first public message (not an internal note) in a conversation that has status "open", the system automatically:

  1. Sets assignedAgentId to the replying agent
  2. Changes status from "open" to "assigned"
  3. Broadcasts a ConversationUpdated event to the tenant group

This eliminates the need for agents to manually claim conversations before responding — the act of responding is the claim.

Auto-Assignment Trigger
Agent sends message in "open" conversation
  → if first public reply by any agent:
      → conversation.assignedAgentId = sender.agentId
      → conversation.status = "assigned"
      → emit ConversationUpdated event

Agent sends message in "assigned" conversation
  → no status change (already assigned)

Agent sends internal note
  → never triggers auto-assignment

Internal Notes

Agents can send internal notes within a conversation — messages visible only to other agents, never to the visitor. Internal notes are useful for:

  • Collaborating with teammates on a tricky issue
  • Leaving context for the next shift
  • Documenting investigation steps
  • Flagging potential escalation needs
Internal Note Message
{
  "id": "msg_internal01",
  "conversationId": "conv_abc123",
  "content": "This customer has been charged twice — check Stripe dashboard",
  "senderType": "agent",
  "senderName": "Mike Lead",
  "senderId": "agt_def456",
  "isInternal": true,
  "createdAt": "2024-01-15T14:29:30Z"
}
⚠️Internal notes do NOT trigger auto-assignment and are never delivered via the visitor's SignalR connection. The security boundary is enforced server-side.

Converting to Ticket

When a conversation requires follow-up work that extends beyond the real-time chat (bug investigation, feature request tracking, etc.), agents can convert it to a ticket:

Conversion Flow
Agent clicks "Convert to Ticket" in dashboard
  → POST /conversations/{id}/convert-to-ticket
  → New ticket created with reference (ZT-1044)
  → Ticket linked back to conversation via conversationId
  → Conversation can continue or be closed independently
  → Ticket has its own lifecycle (open → resolved → closed)

A conversation can only be converted once. The ticket maintains a link back to the conversation for reference, and the conversation entity stores the linked ticket ID.

Real-Time Events

Every lifecycle transition emits SignalR events so connected clients stay in sync:

  • New conversationConversationCreated to tenant group
  • Status changeConversationUpdated to tenant + conversation group
  • Agent assignedConversationUpdated to tenant + conversation group
  • New messageMessageReceived to conversation group (+ tenant for notes)
  • TypingTypingChanged to conversation group