Two things make a CTI app more than a remote control. It can decide where calls go, and it can see what calls are doing. This page covers both. First, register your app as a route point, so the engine asks you for a destination on each inbound call. Second, subscribe to the CSTA event stream, so you observe the real call-state changes that your verbs (and everyone else’s) produce. Both build on a connected CTI leg. If you have not connected yet, start with the CTI overview. You need a session authenticated with the routing scope for routeRegister. Any authenticated CTI leg can subscribe to the event stream.

Verbs change state; events report it

The CSTA verb API is request/reply. You send a verb (holdCall, singleStepTransfer, makeCall, …). The engine replies on the same correlated line to say that it accepted the request. That reply is an acknowledgement, not the outcome. The actual hold, connect, or clear is an asynchronous state change. The engine reports it to you as an event on the cti/<tenant> stream. It reports the same event to every other observer of that tenant.
Treat the verb reply as “the engine took my request”. Treat the event as “the call reached this state.” A holdCall reply tells you that the engine accepted the hold. The matching held event tells you that the call is now held. Drive your app state off the events, not the verb replies.
This split is why the event stream matters even for a pure call-control app. Transfers complete, callers hang up, and consult legs answer without any verb of yours. Only the event stream tells you.

The CSTA event stream

CSTA events ride a durable per-tenant MoQT track, cti/<tenant>. Your SDK subscribes to it directly over the same connection as the rest of the SDK. The track is durable, so a briefly disconnected client resumes without loss of the events that it missed. Every observer of a tenant sees the same ordered stream. Each event names a kind. It carries the identifiers that you need to correlate it back to a call and connection:
Each event carries at least the callId that it concerns. You can then correlate it to the verb that you issued or to the route that you selected. Events also carry a CSTA cause. The cause lets you distinguish, for example, a normal cleared from an abandoned one. The exact field layout is documented in the CTI overview. The examples below key only off kind and callId, which are always present.

Register as a route point

A route point (a CSTA route dialog) inverts control. Instead of you telling the engine what to do with a known call, the engine asks you where an inbound call should go, and it waits for your answer. This is how you implement ACD-style third-party routing in your own app: skills-based selection, data-directed routing, or custom queueing. The engine keeps ownership of the media and signalling. You claim that role with routeRegister, which needs the routing scope:
number
required
Per-request decision deadline. When the engine hands you a routing request, you have this long to select a destination. If you do not answer in time, the engine stops the wait and applies its default handling for that call. A slow or stuck route point therefore never wedges an inbound call.
number
required
Lease duration for the registration itself. The engine treats your app as the route point for this many seconds. Renew the lease: call routeRegister again before it expires. If your app dies without renewal, the lease lapses. Routing then falls back to default handling instead of a hang on a dead route point.
After registration, routing requests for the tenant arrive on the same cti/<tenant> event stream that you already subscribe to. For each request, inspect the call (caller, dialed number, attached data). Then answer with the destination: an agent, a queue/skill, a number, or a trunk leg. Reject the request, or let it time out, to fall through to default handling.
A route point is a live decision loop, not fire-and-forget. Renew the registration within ttlSec. Answer every request within timeoutMs. A route point that stalls silently degrades every inbound call to default routing until its lease expires.
The wire verb that returns your chosen destination is part of the CSTA route dialog. It is documented with the full verb schema in the CTI overview. The loop below shows where that reply goes. Substitute the exact select verb and its argument shape from the reference for your SDK version.

Subscribe and run the route loop

The pattern is:
  1. Connect and authenticate the CTI leg (see the overview).
  2. Subscribe to the cti/<tenant> track.
  3. Call routeRegister once to claim the route point.
  4. Loop over events. Answer routing requests and react to call-state changes.
Frames are the correlated <verb>\t<args-json> lines described in the overview.
subscribeEvents / subscribe_events and selectRoute / select_route above stand in for your SDK version’s exact names. The protocol fixes these parts: the routeRegister verb, its { timeoutMs, ttlSec } arguments, the cti/<tenant> track, and the event kinds. Check the SDK method names against the CTI reference before you ship.

CTI (CSTA) overview

Connect, authenticate, read the scopes, and see the full event/verb schema.

CSTA call control

Use the verbs that change call state: make, hold/retrieve, transfer, consult, clear.

CSTA agent state

Log agents in and out. Set ready / not-ready so routing has targets.

High availability

Routing and CTI control continue to resolve through a node failure.