Receber uma chamada recebida

As chamadas recebidas são encaminhadas aos clientes com sessões ativas após a API da Vonage executar o fluxo do webhook de atendimento.

Caminho de entrada

Aviso: Registre os ouvintes primeiro Registrar ouvintes de convites para chamadas antes criar ou restabelecer uma sessão, para que os convites recebidos não sejam perdidos.

Atendimento a convites de chamadas recebidas

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;
     }
 }

Comportamento ao clicar em “Convidar” ou “Cancelar”

Os convites podem ser cancelados ou expirar antes que o usuário realize alguma ação. Seu aplicativo recebe uma chamada de retorno de cancelamento com um dos seguintes motivos:

Motivo Descrição
RemoteCancel A pessoa que ligou desligou antes que o convite fosse aceito.
RemoteTimeout O convite expirou na API da Vonage sem que o usuário tivesse feito nada.
AnsweredElsewhere O mesmo usuário atendeu a chamada em outro dispositivo ou instância do cliente.
RejectedElsewhere O mesmo usuário rejeitou a chamada em outro dispositivo ou instância do cliente.

Orientações sobre UX

  • Fechar imediatamente a interface de chamada recebida assim que for apresentado qualquer motivo para cancelamento.
  • Não tente ligar answer() ou reject() após receber uma notificação de cancelamento — o convite não é mais válido.
  • Para cenários com vários dispositivos (AnsweredElsewhere / RejectedElsewhere), considere exibir uma breve mensagem ou um indicador visual para que o usuário entenda por que o toque parou.

Tratamento de erros

answer() e reject() pode falhar se a sessão tiver expirado, se o convite não for mais válido (já tiver sido cancelado) ou se houver um problema de rede. Sempre trate o callback de erro para atualizar sua interface de usuário de acordo:

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()
}

Se answer() Se falhar devido a uma sessão expirada, recrie a sessão com um JWT novo e tente novamente. Se o convite já tiver sido cancelado (o chamador desligou), não há como recuperar — feche a interface do usuário e aguarde o próximo convite.

Leitura complementar

  • Referência NCCO — documentação completa sobre a estrutura JSON que o webhook de resposta retorna