Inbound Voice: Alerting, CCM, Accept, and Topic Subscription
This page is an add-on to Custom Agent Desk Developer Guide 2.0 section 4 (Push Mode Tasks). Voice ringing is SIP-driven. After the agent accepts, CCM starts the reserved voice task, and Agent Manager then drives the conversation UI through the same topicSubscription / onTopicData path used for chat.
Voice does not use a reserved taskRequest to ring the agent. The offer is FS JS newInboundCall (ALERTING). taskRequest with taskState.name = STARTED arrives after accept, and that is when the desk subscribes to the conversation topic.
End-to-end flow
SIP INVITE arrives at registered agent extension
↓
FS JS event: newInboundCall (dialog.state = ALERTING)
↓
Custom desk shows ringing UI (Accept / Reject)
↓
POST CCM /message/receive intent = CALL_ALERTING (dialog payload)
↓
Agent clicks Accept → FS JS command: answerCall
↓
FS JS event: dialogState (dialog.state = ACTIVE)
↓
POST CCM /message/receive intent = CALL_LEG_STARTED (accepted dialog payload)
↓
CCM / Routing Engine changes the voice task state to STARTED
↓
Socket.IO event: taskRequest (taskState.name = STARTED)
↓
Custom desk emits: topicSubscription
↓
Agent Manager emits: onTopicData
↓
Custom desk updates conversation UI from onTopicData
↓
Call ends → FS JS dialogState (DROPPED)
↓
POST CCM /message/receive intent = CALL_LEG_ENDED
CCM API
All three voice CIM intents use the same CCM endpoint:
POST {CCM_URL}/message/receive
Content-Type: application/json
The desk switches intent on the same CIM envelope: CALL_ALERTING while ringing, CALL_LEG_STARTED after accept, CALL_LEG_ENDED when the dialog is dropped.
Step 1 — Ringing / alerting (push to CCM)
When newInboundCall arrives with dialog.state = ALERTING, show the ringing card. At the same time POST a CIM message to CCM so CCM knows the agent is being alerted:
POST {CCM_URL}/message/receive
Set header.intent to CALL_ALERTING. Put the SIP dialog on body.dialog (callId = dialog.id, customer number, service identifier, and leg). Do not wait for a chat-style reserved taskRequest to ring the agent. For the voice MRD (CX VOICE), a reserved taskRequest is ignored for ringing.
Sample payload — intent: CALL_ALERTING
{
"id": "50c79b2e-72c5-4882-ad10-ee6814d381bb",
"header": {
"channelData": {
"channelCustomerIdentifier": "1002",
"serviceIdentifier": "2555",
"additionalAttributes": []
},
"customer": {
"_id": "6a86a304d32c74310ca0b33f",
"firstName": "1002",
"phoneNumber": [],
"voice": [
"1002"
],
"isAnonymous": true,
"timezoneSource": "UNRESOLVED",
"timezoneConfidence": "UNRESOLVED_TIMEZONE",
"timezone": null,
"region": "",
"__v": 0
},
"language": {},
"timestamp": 1787208634000,
"securityInfo": {},
"stamps": [],
"intent": "CALL_ALERTING",
"entities": {},
"sender": {
"id": "d07d020b-e7e8-4685-9b7e-3e8ba1ef8e16",
"senderName": "regression1",
"type": "AGENT",
"additionalDetail": {}
}
},
"body": {
"type": "VOICE",
"markdownText": null,
"reasonCode": "INBOUND",
"leg": "hbiq7ptr2ljpm2fjf0oc:2001:1002:1787208634000",
"callId": "hbiq7ptr2ljpm2fjf0oc",
"dialog": {
"id": "hbiq7ptr2ljpm2fjf0oc",
"ani": "1002",
"customerNumber": "1002",
"associatedDialogUri": null,
"callbackNumber": null,
"outboundClassification": null,
"scheduledCallbackInfo": null,
"isCallEnded": 0,
"eventType": "PUT",
"callType": "OTHER_IN",
"queueName": "voice regression",
"queueType": "NAME",
"dialedNumber": "2555",
"dnis": "2555",
"serviceIdentifier": "2555",
"secondaryId": null,
"state": "ALERTING",
"isCallAlreadyActive": false,
"wrapUpReason": null,
"wrapUpItems": null,
"callEndReason": null,
"fromAddress": "1002",
"callVariables": {
"CallVariable": []
},
"participants": [
{
"actions": {
"action": [
"ANSWER"
]
},
"mediaAddress": "2001",
"mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow",
"startTime": null,
"alertingTime": "2026-08-20T06:50:34.985Z",
"state": "ALERTING",
"stateCause": null,
"stateChangeTime": "2026-08-20T06:50:34.985Z",
"mute": false
}
],
"mediaType": "audio",
"channelType": "VOICE",
"primaryDN": null
}
}
}
Step 2 — Accept (dialog to CCM, task → STARTED)
Send FS JS answerCall with the alerting dialogId (Developer Guide section 5.4). When the callback returns dialogState with dialog.state = ACTIVE, POST the accepted dialog to CCM again:
POST {CCM_URL}/message/receive
Set header.intent to CALL_LEG_STARTED, using the same dialog / leg / callId fields as alerting. CCM uses this accept message to move the voice task from reserved/alerting to STARTED.
Sample payload — intent: CALL_LEG_STARTED
{
"id": "3ac3a79f-b603-410b-849b-7e70c1937e5e",
"header": {
"channelData": {
"channelCustomerIdentifier": "1002",
"serviceIdentifier": "2555",
"additionalAttributes": []
},
"customer": {
"_id": "6a86a304d32c74310ca0b33f",
"firstName": "1002",
"phoneNumber": [],
"voice": [
"1002"
],
"isAnonymous": true,
"timezoneSource": "UNRESOLVED",
"timezoneConfidence": "UNRESOLVED_TIMEZONE",
"timezone": null,
"region": "",
"__v": 0
},
"language": {},
"timestamp": 1787208639000,
"securityInfo": {},
"stamps": [],
"intent": "CALL_LEG_STARTED",
"entities": {},
"sender": {
"id": "d07d020b-e7e8-4685-9b7e-3e8ba1ef8e16",
"senderName": "regression1",
"type": "AGENT",
"additionalDetail": {}
}
},
"body": {
"type": "VOICE",
"markdownText": null,
"reasonCode": "INBOUND",
"leg": "hbiq7ptr2ljpm2fjf0oc:2001:1002:1787208634000",
"callId": "hbiq7ptr2ljpm2fjf0oc",
"dialog": {
"id": "hbiq7ptr2ljpm2fjf0oc",
"ani": "1002",
"customerNumber": "1002",
"associatedDialogUri": null,
"callbackNumber": null,
"outboundClassification": null,
"scheduledCallbackInfo": null,
"isCallEnded": 0,
"eventType": "PUT",
"callType": "OTHER_IN",
"queueName": "voice regression",
"queueType": "NAME",
"dialedNumber": "2555",
"dnis": "2555",
"serviceIdentifier": "2555",
"secondaryId": null,
"state": "ACTIVE",
"isCallAlreadyActive": false,
"wrapUpReason": null,
"wrapUpItems": null,
"callEndReason": null,
"fromAddress": "1002",
"callVariables": {
"CallVariable": []
},
"participants": [
{
"actions": {
"action": [
"ANSWER"
]
},
"mediaAddress": "2001",
"mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow",
"startTime": "2026-08-20T06:50:39.998Z",
"alertingTime": "2026-08-20T06:50:34.985Z",
"state": "ACTIVE",
"stateCause": null,
"stateChangeTime": "2026-08-20T06:50:39.998Z",
"mute": false
}
],
"mediaType": "audio",
"channelType": "VOICE",
"primaryDN": null
}
}
}
Step 3 — Topic subscription (conversation UI)
After the task is started, Agent Manager emits taskRequest with taskState.name = "STARTED". The custom desk must then emit topicSubscription (same payload shape as chat accept in Developer Guide section 4.b):
socket.emit("topicSubscription", {
topicParticipant: {
type: "AGENT",
participant: currentAgent,
conversationId: taskRequest.conversationId,
role: taskRequest.taskDirection === "CONSULT" ? "ASSISTANT" : "PRIMARY",
state: "SUBSCRIBED"
},
agentId: currentAgent.id,
conversationId: taskRequest.conversationId,
roomInfo: taskRequest.roomInfo,
taskId: taskRequest.taskId
});
Agent Manager responds with onTopicData (topicData, conversationId, roomInfo, taskId). Use that payload to open / refresh the conversation view (customer, channel sessions, gadgets, history). Call-control buttons stay on SIP dialogState (Developer Guide section 5.19); the conversation panel comes from onTopicData.
If taskRequest.taskDirection is consult, subscribe as ASSISTANT. If it is silent_monitor, subscribe as SILENT_MONITOR. Otherwise subscribe as PRIMARY.
Step 4 — Call ended (push to CCM)
When the SIP dialog is DROPPED, POST CIM CALL_LEG_ENDED to the same CCM API. Include header.conversationId, body.reasonCode (for a normal inbound hangup: DIALOG_ENDED), body.endingReason (from dialog.callEndReason, e.g. AGENT-HANDLED), and conversationId as a channelData.additionalAttributes entry.
POST {CCM_URL}/message/receive
Sample payload — intent: CALL_LEG_ENDED
{
"id": "6ec50995-6354-4608-9626-a9e7a1139402",
"header": {
"channelData": {
"channelCustomerIdentifier": "1002",
"serviceIdentifier": "2555",
"additionalAttributes": [
{
"key": "conversationId",
"type": "String2000",
"value": "6a86a3bbfe8ac52c88a8ac79"
}
]
},
"customer": {
"_id": "6a86a304d32c74310ca0b33f",
"firstName": "1002",
"phoneNumber": [],
"voice": [
"1002"
],
"isAnonymous": true,
"timezoneSource": "UNRESOLVED",
"timezoneConfidence": "UNRESOLVED_TIMEZONE",
"timezone": null,
"region": "",
"__v": 0
},
"language": {},
"timestamp": 1787208645000,
"securityInfo": {},
"stamps": [],
"intent": "CALL_LEG_ENDED",
"entities": {},
"sender": {
"id": "d07d020b-e7e8-4685-9b7e-3e8ba1ef8e16",
"senderName": "regression1",
"type": "AGENT",
"additionalDetail": {}
},
"conversationId": "6a86a3bbfe8ac52c88a8ac79"
},
"body": {
"type": "VOICE",
"markdownText": null,
"reasonCode": "DIALOG_ENDED",
"leg": "hbiq7ptr2ljpm2fjf0oc:2001:1002:1787208634000",
"callId": "hbiq7ptr2ljpm2fjf0oc",
"dialog": {
"id": "hbiq7ptr2ljpm2fjf0oc",
"ani": "1002",
"customerNumber": "1002",
"associatedDialogUri": null,
"callbackNumber": null,
"outboundClassification": null,
"scheduledCallbackInfo": null,
"isCallEnded": 1,
"eventType": "PUT",
"callType": "OTHER_IN",
"queueName": "voice regression",
"queueType": "NAME",
"dialedNumber": "2555",
"dnis": "2555",
"serviceIdentifier": "2555",
"secondaryId": null,
"state": "DROPPED",
"isCallAlreadyActive": false,
"wrapUpReason": null,
"wrapUpItems": null,
"callEndReason": "AGENT-HANDLED",
"fromAddress": "1002",
"callVariables": {
"CallVariable": []
},
"participants": [
{
"actions": {
"action": [
"ANSWER"
]
},
"mediaAddress": "2001",
"mediaAddressType": "SIP.js/0.21.2-CTI/Expertflow",
"startTime": "2026-08-20T06:50:39.998Z",
"alertingTime": "2026-08-20T06:50:34.985Z",
"state": "DROPPED",
"stateCause": null,
"stateChangeTime": "2026-08-20T06:50:45.022Z",
"mute": false
}
],
"mediaType": "audio",
"channelType": "VOICE",
"primaryDN": null
},
"endingReason": "AGENT-HANDLED"
}
}
Summary
|
Stage |
What the desk receives |
What the desk sends |
Result |
|---|---|---|---|
|
Call offered |
FS JS |
CIM |
Ringing UI; CCM records alerting |
|
Agent accepts |
FS JS |
FS JS |
Voice task state becomes |
|
Conversation UI |
Socket |
Socket |
Customer / conversation view opens |
|
Call ended |
FS JS |
CIM |
Voice leg closed; |
Reference implementation in Agent Desk: sip.service.ts (handleCallAlertingEvent, acceptCallOnSip, handleCallActiveEvent, handleCallDroppedEvent, ccmChannelSessionApi) and socket.service.ts (taskRequest when taskState.name == started → topicSubscription → onTopicData).