Event Lifecycle
Understanding how events flow through an interaction's lifecycle helps you build robust integrations and accurately track interaction progress.
Overview
Contact center interactions (calls, chats, emails) progress through a series of states, with each state transition generating an event. By tracking these events and their sequence, you can:
- Build real-time dashboards showing interaction status
- Calculate key metrics (queue time, talk time, wrap-up duration)
- Correlate events to understand complete customer journeys
- Identify patterns and optimize routing rules
Inbound Call Lifecycle
A typical inbound phone call generates events in this sequence:
Key Fields to Track:
interactionGUID: Unique identifier to correlate all events for this interactioncallState: Current state in the lifecycleinteractionEventTS: When each transition occurred. NoteventTS, which is the time the interaction was offered and is the same on every event (see Choosing a Timestamp)agentId: Which agent handled the interaction (from InteractionAssigned onward)
Outbound Call Lifecycle
Outbound calls (agent-initiated or campaign) have a slightly different flow:
Key Difference: Outbound calls skip the InteractionQueued state and use InteractionCustomerAccepted when the customer answers.
Rejected/Abandoned Call Flow
Not all interactions complete successfully. Here are common alternate flows:
Agent Rejects Interaction
When an agent rejects an offered interaction, it returns to the queue and may be offered to another agent.
Customer Abandons Queue
If a customer hangs up while waiting in queue, you'll see InteractionUnqueued followed by InteractionDeleted.
Outbound Call Not Answered
When an outbound call doesn't connect, the InteractionDeleted event includes diagnostic information about why the call failed.
Agent State Events
In parallel with interaction events, agent state changes are tracked separately:
Note: Agent events are published to:
tenant/event-v1/all(all events topic)tenant/event-v1/AgentStatusChange(event-specific topic)tenant/agent-v1/{agentId}(agent-specific topic)
Tracking Complete Interactions
To track a complete interaction from start to finish:
-
Use
interactionGUIDas the correlation key across all events -
Track state transitions via
callStatefield -
Monitor timestamps using
interactionEventTSto calculate durations:- Queue time: InteractionQueued.interactionEventTS → InteractionAssigned.interactionEventTS
- Ring time: InteractionAssigned.interactionEventTS → InteractionAccepted.interactionEventTS
- Talk time: InteractionAccepted.interactionEventTS → InteractionPostProcess.interactionEventTS
- Wrap-up time: InteractionPostProcess.interactionEventTS → InteractionEndPostProcess.interactionEventTS
Use
interactionEventTSfor these, noteventTS.eventTSrecords when the interaction was offered, so it is identical on every event for that interaction and each of the subtractions above would evaluate to zero. See Choosing a Timestamp. -
Capture outcome from InteractionDeleted:
hangupInitiator: Who ended the callcallHangupReason: Why the call endeddispositionCode: Agent's wrap-up disposition
Example: Building an Interaction Timeline
Here's a practical example of tracking interaction metrics by correlating events:
- Go
- Python
- Node.js
type InteractionTimeline struct {
InteractionGUID string
Created time.Time
Queued time.Time
Assigned time.Time
Connected time.Time
Disconnected time.Time
AgentID string
QueueID int
HangupReason string
}
func (t *InteractionTimeline) ProcessEvent(event Event) {
// interactionEventTS is absent on some event types, where it unmarshals to 0.
// Leave eventTime as the zero Time so the IsZero() guards below catch it -
// time.Unix(0, 0) is 1970, which is NOT IsZero() and would slip through.
var eventTime time.Time
if event.InteractionEventTS > 0 {
eventTime = time.Unix(event.InteractionEventTS, 0)
}
switch event.Event {
case "InteractionCreated":
t.InteractionGUID = event.InteractionGUID
t.Created = eventTime
case "InteractionQueued":
t.Queued = eventTime
t.QueueID = event.QueueID
case "InteractionAssigned":
t.Assigned = eventTime
t.AgentID = event.AgentID
case "InteractionAccepted", "InteractionCustomerAccepted":
t.Connected = eventTime
case "InteractionDeleted":
t.Disconnected = eventTime
t.HangupReason = event.CallHangupReason
}
}
func (t *InteractionTimeline) QueueDuration() time.Duration {
if t.Queued.IsZero() || t.Assigned.IsZero() {
return 0
}
return t.Assigned.Sub(t.Queued)
}
func (t *InteractionTimeline) TalkDuration() time.Duration {
if t.Connected.IsZero() || t.Disconnected.IsZero() {
return 0
}
return t.Disconnected.Sub(t.Connected)
}
from datetime import datetime
from dataclasses import dataclass
from typing import Optional
@dataclass
class InteractionTimeline:
interaction_guid: Optional[str] = None
created: Optional[datetime] = None
queued: Optional[datetime] = None
assigned: Optional[datetime] = None
connected: Optional[datetime] = None
disconnected: Optional[datetime] = None
agent_id: Optional[str] = None
queue_id: Optional[int] = None
hangup_reason: Optional[str] = None
def process_event(self, event: dict):
event_type = event.get('event')
# interactionEventTS is absent on some event types - leave the time as None
# rather than defaulting to 0, which would decode as 1970
event_ts = event.get('interactionEventTS')
event_time = datetime.fromtimestamp(event_ts) if event_ts else None
if event_type == 'InteractionCreated':
self.interaction_guid = event['interactionGUID']
self.created = event_time
elif event_type == 'InteractionQueued':
self.queued = event_time
self.queue_id = event.get('queueId')
elif event_type == 'InteractionAssigned':
self.assigned = event_time
self.agent_id = event.get('agentId')
elif event_type in ['InteractionAccepted', 'InteractionCustomerAccepted']:
self.connected = event_time
elif event_type == 'InteractionDeleted':
self.disconnected = event_time
self.hangup_reason = event.get('callHangupReason')
def queue_duration(self) -> Optional[float]:
if self.queued and self.assigned:
return (self.assigned - self.queued).total_seconds()
return None
def talk_duration(self) -> Optional[float]:
if self.connected and self.disconnected:
return (self.disconnected - self.connected).total_seconds()
return None
class InteractionTimeline {
constructor() {
this.interactionGUID = null;
this.created = null;
this.queued = null;
this.assigned = null;
this.connected = null;
this.disconnected = null;
this.agentId = null;
this.queueId = null;
this.hangupReason = null;
}
processEvent(event) {
// interactionEventTS is absent on some event types - leave the time as null
// rather than defaulting to 0, which would decode as 1970
const eventTime = event.interactionEventTS
? new Date(event.interactionEventTS * 1000)
: null;
switch (event.event) {
case 'InteractionCreated':
this.interactionGUID = event.interactionGUID;
this.created = eventTime;
break;
case 'InteractionQueued':
this.queued = eventTime;
this.queueId = event.queueId;
break;
case 'InteractionAssigned':
this.assigned = eventTime;
this.agentId = event.agentId;
break;
case 'InteractionAccepted':
case 'InteractionCustomerAccepted':
this.connected = eventTime;
break;
case 'InteractionDeleted':
this.disconnected = eventTime;
this.hangupReason = event.callHangupReason;
break;
}
}
queueDuration() {
if (this.queued && this.assigned) {
return (this.assigned - this.queued) / 1000; // seconds
}
return null;
}
talkDuration() {
if (this.connected && this.disconnected) {
return (this.disconnected - this.connected) / 1000; // seconds
}
return null;
}
}
Best Practices
Event Correlation
- Always use
interactionGUIDto correlate events - it's the only reliable way to track an interaction across its entire lifecycle - Store events rather than just metrics - you may need to recalculate or debug later
- Handle out-of-order events - network delays can cause events to arrive out of sequence
State Management
- Track
callStatetransitions to ensure your application state matches the interaction state - Expect optional events - not all interactions follow the complete path (e.g., abandoned calls skip most steps)
- Handle missing events - network issues or system restarts may cause missed events
Metric Calculation
- Use
interactionEventTSfor duration calculations - noteventTS, which does not advance between events (see Choosing a Timestamp) - Handle events with no
interactionEventTS- usemsgInfo.timestamprather than defaulting a missing value to 0 - Account for holds -
LineHoldStatusevents affect talk time calculations - Consider time zones - timestamps are in UTC, convert as needed for reporting
Error Handling
- Check
hangupInitiatorandcallHangupReasonin InteractionDeleted events to understand why interactions ended - Monitor for anomalies - unexpected state transitions may indicate system issues
- Log unhandled event types - new event types may be added in future releases
Next Steps
- Message Format - Detailed field descriptions and message structure
- Event Reference - Complete list of event types and their fields
- Field Reference - Detailed documentation for all event fields
- Code Examples - Complete working implementations