Skip to main content
POST /webhooks/call_control_application/<id>. channelRoutes verifies the signature, then the call handler in channels/index.ts acts on call.initiated for an incoming leg.
  1. It wakes the call’s instance, warmCall(from) on the conversation named by the call’s id, so that the instance is placed and running and the caller’s memory read has begun. The caller hears ringing meanwhile, typically one to three seconds.
  2. It answers the inbound leg with the media stream requested in the same command, so the media starts the moment the leg is up.
  3. The media server opens a WebSocket to /agents/<name>/<call_control_id>. mountAgents hands it to the instance’s webSocket(), where the voice class answers the call.
The first two steps are the handler’s own:
calls is createCallControlClient() from @telnyx/edge-runtime/voice. Its command goes out over the runtime’s own connection, so the function holds no key. warm calls warmCall on the instance’s stub, as The Messaging Webhook shows for a text. The instance takes the call in managed/voice.ts:
webSocket(ws) overrides the runtime SDK’s webSocket().
  • It never calls super.webSocket(), so the socket carries the call’s media rather than the agent socket protocol.
  • this.answer(ws, answerOptions(spec)) is the voice() mixin’s answer. It takes the media socket and the options from voice, and runs the call from then on.
  • Both methods start the turn engine’s first reading, the harness’s ready() or the direct engine’s warm(), so that the first turn does not wait for it.
  • warmCall carries no @rpc, so over the mount it is private, and only the handler reaches it.
streaming.started, streaming.failed and call.hangup on the answered leg are logged as one line each; a call that connects but stays silent explains itself in streaming.failed. The Voice Loop is the turn taking that follows.