Skip to content

Receive an inbound call

Inbound calls are delivered to clients with active sessions after the Vonage API executes your answer webhook flow.

Inbound path

Register listeners first

Register call invite listeners before creating or reconnecting a session, so incoming invites are not missed.

Handling inbound invites


 // Answer Call
 client.setCallInviteListener {inviteId, from, channelType ->
     println("Incoming Call from $from by channel $channelType")
     client.answer(inviteId) {
         it?.let {err ->
             // Handle Error in answering call
             println("Error in answering call: $err")
         }
     }
 }

 // Or reject the call:
 // client.setCallInviteListener { inviteId, from, channelType ->
 //     client.reject(inviteId) {
 //         it?.let {err ->
 //             println("Error in rejecting call: $err")
 //         }
 //     }
 // }

 // Invite Cancelled
 client.setCallInviteCancelListener { inviteId, reason ->
         when (reason) {
             VoiceInviteCancelReason.RemoteCancel -> println("Call Invite for call $inviteId was cancelled by the caller")
             VoiceInviteCancelReason.AnsweredElsewhere -> println("Call Invite for call $inviteId was answered on another device")
             VoiceInviteCancelReason.RejectedElsewhere -> println("Call Invite for call $inviteId was rejected on another device")
             VoiceInviteCancelReason.RemoteTimeout -> println("Call Invite for call $inviteId timed out")
         }
     }

Invite cancel behavior

Invites can be canceled or time out before user action. Your app receives a cancel callback with one of the following reasons:

ReasonDescription
RemoteCancelThe caller hung up before the invite was answered.
RemoteTimeoutThe invite expired on the Vonage API without user action.
AnsweredElsewhereThe same user answered the call on another device or client instance.
RejectedElsewhereThe same user rejected the call on another device or client instance.

UX guidance

  • Dismiss incoming call UI immediately when any cancel reason arrives.
  • Do not attempt to call answer() or reject() after receiving a cancel event — the invite is no longer valid.
  • For multi-device scenarios (AnsweredElsewhere / RejectedElsewhere), consider showing a brief toast or visual indicator so the user understands why the ringing stopped.

Handling errors

answer() and reject() can fail if the session has expired, the invite is no longer valid (already cancelled), or there is a network issue. Always handle the error callback to update your UI accordingly:

kotlin
client.answer(callId) { error ->
    if (error != null) {
        // Answer failed — dismiss call UI and inform the user
        println("Answer failed: ${error.message}")
        // Common causes:
        // - Session expired (re-create session and retry)
        // - Invite already cancelled by caller
        dismissIncomingCallUI()
        return@answer
    }
    // Success — transition to active call UI
    showActiveCallUI()
}

client.reject(callId) { error ->
    if (error != null) {
        // Reject failed — the invite may have already been cancelled
        println("Reject failed: ${error.message}")
    }
    // In both success and failure, dismiss the incoming call UI
    dismissIncomingCallUI()
}

Recovery strategy

If answer() fails due to an expired session, re-create the session with a fresh JWT and retry the answer. If the invite has already been cancelled (caller hung up), there is no recovery — dismiss the UI and wait for the next invite.

Further reading

  • NCCO reference — full documentation for the JSON structure your answer webhook returns

Built with VitePress.