Recibir una llamada entrante

Las llamadas entrantes se transfieren a los clientes con sesiones activas una vez que la API de Vonage ejecuta el flujo de tu webhook de respuesta.

Ruta de entrada

Advertencia: Registra primero los oyentes Registrar llamadas, invitar a oyentes antes de crear o restablecer una sesión, para no perder ninguna invitación entrante.

Gestión de las invitaciones de llamadas entrantes

client.on('callInvite', async (callId, from, channelType) => {
  console.log(`Received call invite from ${from} on ${channelType}`);

  // To accept the call:
  await client.answer(callId);

  // Or to reject the call:
  // await client.reject(callId);
});

client.on('callInviteCancel', (callId, reason) => {
  if (reason === CancelReason.AnsweredElsewhere) {
    console.log(`Call ${callId} was answered elsewhere`);
  } else if (reason === CancelReason.RejectedElsewhere) {
    console.log(`Call ${callId} was rejected elsewhere`);
  } else if (reason === CancelReason.RemoteCancel) {
    console.log(`Call ${callId} was cancelled by the caller`);
  } else if (reason === CancelReason.RemoteTimeout) {
    console.log(`Call ${callId} timed out`);
  }
});

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

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

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

 // Answer Call
 client.setCallInviteListener((callId, from, channelType) -> {
     System.out.println("Incoming Call from " + from + " by channel " + channelType);
     client.answer(callId, it -> {
         if (it != null) {
             // Handle Error in answering call
             System.out.println("Error in answering call: " + it);
         }
         return null;
     });
     return null;
 });

 // Or reject the call:
 // client.setCallInviteListener((callId, from, channelType) -> {
 //     client.reject(callId, it -> {
 //         if (it != null) {
 //             System.out.println("Error in rejecting call: " + it);
 //         }
 //         return null;
 //     });
 //     return null;
 // });

 // Invite Cancelled
 client.setCallInviteCancelListener((callId, reason) -> {
     switch (reason) {
         case RemoteCancel:
             System.out.println("Call Invite for call " + callId + " was cancelled by the caller");
             break;
         case AnsweredElsewhere:
             System.out.println("Call Invite for call " + callId + " was answered on another device");
             break;
         case RejectedElsewhere:
             System.out.println("Call Invite for call " + callId + " was rejected on another device");
             break;
         case RemoteTimeout:
             System.out.println("Call Invite for call " + callId + " timed out");
             break;
     }
     return null;
 });
 
 class inboundCallExampleDelegate: NSObject, VGVoiceClientDelegate {
     
     
     func voiceClient(_ client: VGVoiceClient, didReceiveInviteForCall callId: VGCallId, from caller: String, with type: VGVoiceChannelType) {
         print(caller)
         // Answer Invite
         client.answer(callId) { error in
             guard error != nil else {
                 print("Failed to answer \(error?.localizedDescription ?? "")")
                 return
             }
         }
         // Or reject the invite:
         // client.reject(callId) { error in
         //     guard error != nil else {
         //         print("Failed to reject \(error?.localizedDescription ?? "")")
         //         return
         //     }
         // }
     }
     func voiceClient(_ client: VGVoiceClient, didReceiveInviteCancelForCall callId: VGCallId, with reason: VGVoiceInviteCancelReason) {
         guard callId == "currentCallId" else {
             return
         }
         
         switch reason {
         case .answeredElsewhere:
             print("Call answered elsewhere")
         case .remoteCancel:
             print("Call cancelled by remote")
         case .rejectedElsewhere:
             print("Call rejected elsewhere")
         case .remoteTimeout:
             print("Call timed out")
         default:
             print("reason unknown")
         }
     }
     
     func client(_ client: VGBaseClient, didReceiveSessionErrorWith reason: VGSessionErrorReason) {}
     func voiceClient(_ client: VGVoiceClient, didReceiveHangupForCall callId: VGCallId, withQuality callQuality: VGRTCQuality, reason: VGHangupReason) {}
 }
 

 - (void) voiceClient:(VGVoiceClient *)client didReceiveInviteForCall:(VGCallId)callId from:(NSString *)caller withChannelType:(VGVoiceChannelType)type {
     NSLog(@"Caller: %@", caller);
     // Answer Invite
     [client answer:callId callback:^(NSError * _Nullable error) {
         if(error){
             NSLog(@"Failed to answer: %@", error);
         }
     }];
     // Or reject the invite:
     // [client reject:callId callback:^(NSError * _Nullable error) {
     //     if(error){
     //         NSLog(@"Failed to reject: %@", error);
     //     }
     // }];
 }

 - (void) voiceClient:(VGVoiceClient *)client didReceiveInviteCancelForCall:(VGCallId)callId withReason:(VGVoiceInviteCancelReason)reason {
     if(![callId isEqualToString:@"currentCallId"]){
         return;
     }
     switch(reason) {
         case VGVoiceInviteCancelReasonAnsweredElsewhere:
             NSLog(@"Call answered elsewhere");
             break;
         case VGVoiceInviteCancelReasonRemoteCancel:
             NSLog(@"Call canceled by remote");
             break;
         case VGVoiceInviteCancelReasonRejectedElsewhere:
             NSLog(@"Call rejected elsewhere");
             break;
         case VGVoiceInviteCancelReasonRemoteTimeout:
             NSLog(@"Call timed out");
             break;
         case VGVoiceInviteCancelReasonUnknown:
             NSLog(@"Reason Unknown");
             break;
     }
 }

Comportamiento de «Invitar» y «Cancelar»

Las invitaciones pueden cancelarse o caducar antes de que el usuario realice ninguna acción. Tu aplicación recibe una llamada de retorno de cancelación con uno de los siguientes motivos:

Motivo Descripción
RemoteCancel La persona que llamó colgó antes de que se contestara la llamada.
RemoteTimeout La invitación ha caducado en la API de Vonage sin que el usuario haya realizado ninguna acción.
AnsweredElsewhere El mismo usuario respondió a la llamada en otro dispositivo o en otra instancia del cliente.
RejectedElsewhere El mismo usuario rechazó la llamada en otro dispositivo o instancia del cliente.

Directrices de experiencia de usuario

  • Cerrar inmediatamente la interfaz de usuario de la llamada entrante cuando se produzca cualquier motivo de cancelación.
  • No intentes llamar answer() o reject() Tras recibir una notificación de cancelación, la invitación ya no es válida.
  • En casos en los que se utilizan varios dispositivos (AnsweredElsewhere / RejectedElsewhere), plantéate mostrar un breve mensaje o un indicador visual para que el usuario comprenda por qué ha dejado de sonar.

Gestión de errores

answer() y reject() Puede fallar si la sesión ha caducado, la invitación ya no es válida (se ha cancelado) o hay un problema de red. Gestiona siempre la llamada de retorno de error para actualizar la interfaz de usuario en consecuencia:

try {
  await client.answer(callId);
  showActiveCallUI();
} catch (error) {
  console.error("Answer failed:", error.type, error.message);
  dismissIncomingCallUI();
}

try {
  await client.reject(callId);
} catch (error) {
  console.error("Reject failed:", error.type, error.message);
} finally {
  dismissIncomingCallUI();
}
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()
}
client.answer(callId) { error in
    if let error = error {
        print("Answer failed: \(error.localizedDescription)")
        self.dismissIncomingCallUI()
        return
    }
    self.showActiveCallUI()
}

client.reject(callId) { error in
    if let error = error {
        print("Reject failed: \(error.localizedDescription)")
    }
    self.dismissIncomingCallUI()
}

Si answer() Si falla debido a que la sesión ha caducado, vuelve a crear la sesión con un JWT nuevo e intenta responder de nuevo. Si la invitación ya se ha cancelado (la persona que llama ha colgado), no hay forma de recuperarla: cierra la interfaz de usuario y espera a la siguiente invitación.

Lecturas complementarias

  • Referencia OCNC — documentación completa sobre la estructura JSON que devuelve el webhook de tu respuesta