Perform external transfer while agent is on hold

I would like to know if it is possible to transfer the calling channel to another external call while the agent is on hold and after this external call ends the calling channel returns to the agent channel.

I tried to do it this way:

[applicationmap]

transfer => #4,peer,Gosub(transfer-call,call,1),default

[transfer-call]
;=====================================
exten => call,1,Noop(##### TRANSFERINDO CHAMADA #####)
exten => call,n,ChannelRedirect(${SOURCE_CHANNEL},category,extended_number,1)
exten => call,n,hangup()

I’m not sure that doing things with feature codes that undermine the channel they are used on is officially supported, but you can’t retain both channels using the dialplan version of ChannelRedirect. You need to use the AMI version, which has an option to do a simultaneous redirect on two channels.

Why are you trying to do this; there may be a different solution to the underlying requirement.

It is possible that ARI can help, as well.

The flow I need is
I receive a call that enters a queue and rings an available agent, I want to transfer the calling channel to an external call while the agent is on hold, when the external call ends I want the calling channel to return to the agent

That needs a synchronized redirect, which as I say, can only be done using AMI.

You need to redirect the caller to dialplan that makes the call, using the g option on Dial, then bridges back to the agent, and you need to redirect the agent to the Park application, or to some other activity that keeps them connected. I would expect to do such things through the agent’s PC and AMI, rather than through a feature code.

I think the typical use is an unrecorded call to someone who does nothing but collect payment call details, in the middle of a recorded call to a sales agent. A description at that level of abstraction is what I meant by asking what you are really trying to do.

This is the flow I tried to use with ARI, but Stasis cannot add the originating channel to the transfer bridge I created in the stasis application.

Remembering that this flow that I’m trying to do, is only after the originator is served by the agent.

features.conf =>
[applicationmap]
transferCall => #4,peer,Gosub(transfer-call,call,1),default

custom.conf =>
;=====================================

[transfer-call]

;=====================================

exten => call,1,Noop(##### TRANSFERRING CALL#####)
exten => call,n,Stasis(meu_app_ari)
exten => call,n,hangup()

app.js =>

// Criar servidor WebSocket
const wss = new WebSocket.Server({ port: 8081 });

wss.on(‘connection’, (ws) => {
console.log(‘Cliente WebSocket conectado!’);

ws.on(‘message’, (message) => {
console.log(Mensagem recebida: ${message});
ws.send(Echo: ${message});
});

ws.on(‘close’, () => {
console.log(‘Cliente WebSocket desconectado.’);
});
});

Ari.connect(ARI_URL, USERNAME, PASSWORD)
.then((client) => {
console.log(‘Conectado ao Asterisk ARI!’);

client.on('StasisStart', async (event, channel) => {
  console.log(`📞 Canal recebido no Stasis: ${channel.id}`);
  console.log(`📞 Canal recebido no Stasis: ${channel.name}`);
  
  // Verifica se o canal já está em uma bridge
  bridgeId = await listarBridges(client,channel.id)

  transferCall(channel,bridgeId);
});

let transferInProgress = false; // Flag para evitar múltiplas chamadas

async function listarBridges(client,channelId) {
var bridgeId;
var bridges = await client.bridges.list()
if (bridges.length === 0) {
console.log(‘Nenhuma Bridge ativa no momento.’);
} else {
bridges.forEach(bridge => {
if(bridge.channels.includes(channelId)){
console.log(Bridge ID: ${bridge.id}, Tipo: ${bridge.bridge_type}, Canais: ${bridge.channels});
bridgeId = bridge.id;
}
});
}

    return bridgeId;
}

async function transferCall(originateChannel,bridgeOriId) {
  
  if (!originateChannel || !originateChannel.id) {
    console.error('❌ Erro: canal inválido ou não definido!');
    return;
  }

  if (transferInProgress) {
    console.log('⚠️ Transferência já em andamento. Ignorando solicitação repetida.');
    return;
  }
  transferInProgress = true; // Marca como em andamento

  console.log(`🔀 Transferindo chamada para número externo...`);

  try {
    // Criar uma nova chamada para o número externo
    let externalChannel = await client.channels.originate({
      // endpoint: 'SIP/300', // Substitua pelo número desejado
      endpoint: 'Local/11963001933@Padrao', // Substitua pelo número desejado
      app: 'meu_app_ari',
      appArgs: 'external',
      callerId: originateChannel.caller.id
    });
    
    // Criar uma nova bridge para conectar o chamador e a chamada externa
    let transferBridge = await client.bridges.create({ type: 'mixing' });
    console.log(`🔄 Criando bridge de transferência: ${transferBridge}`);
    
    await client.bridges.addChannel({
      bridgeId: transferBridge.id,
      channel: originateChannel.id
    });

   
    // Quando a chamada externa for atendida, conectamos os canais
    externalChannel.on('StasisStart', async () => {
      console.log('📞 Chamada externa conectada.');

      await client.bridges.addChannel({
        bridgeId: transferBridge.id,
        channel: externalChannel.id
      });
    
    });

    // Quando a chamada externa terminar, reconectar o agente
    externalChannel.on('ChannelHangupRequest', async () => {

      await client.bridges.destroy({
        bridgeId: transferBridge.id
      });

      console.log('❌ Chamada externa finalizada. Retornando agente.');

      // adicionar o chamador da bridge de orignal
      await client.bridges.addChannel({
        bridgeId: bridgeOriId,
        channel: originateChannel.id
      });
      await client.channels.continueInDialplan(
        {channelId: originateChannel.id}
      );


    });
    

  } catch (error) {
    console.error('❌ Erro ao tentar realizar a transferência:', error);
  }
}

client.start(APP_NAME);

})
.catch(console.error);

You haven’t said what SOURCE_CHANNEL is, but potentially that could be the same as the channel on which the feature code is running, in which case it would redirect the feature code execution. If it is the other channel in the bridge, the application that created the bridge will have to exit before the redirect takes effect.

Even if you are doing a double redirect, you will probably need to interpose a local channel, upstream.

I haven’t used ARI, so can’t comment on the specifics of your ARI code.

SOURCE_CHANNEL is the channel of the originator that I am trying to transfer to an external call, I am able to redirect it, my problem is making the originator return to the agent after this external call ends.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.