Crie uma campanha de prompt de saída para facilitar as chamadas automatizadas.
Use a API do 3CX Call Control para automatizar campanhas de chamadas de saída. Conecte uma lista de números de telefone a um IVR (URA), reproduza uma mensagem automatizada e transfira chamadas para diferentes destinos com base nas seleções do menu URA. Ao contrário da discagem de saída tradicional, esse método oferece flexibilidade em mensagens, encaminhamento de chamadas e integração com CRM ou banco de dados. Continue lendo para saber mais e como começar.
Quando Usar o Script de Campanha de Prompts de Saída

O cancelamento de um voo é um exemplo perfeito. Uma companhia aérea pode notificar os passageiros por meio de uma mensagem gravada e oferecer opções de menu para conectá-los ao suporte.
Esse é um caso de uso básico, mas pode ser ampliado. Você pode criar uma URA personalizada com tratamento de entrada DTMF e controle de fluxo de áudio para campanhas de saída.
Consulte o repositório Oficial do 3CX no GitHub para obter mais exemplos
Configuração de Tratamento de Chamadas e Integração de API
Crie uma URa 3CX, adicione-a ao Call Control API Access e selecione-o na lista Extensions (Extensões).
Início da Chamada
A interface do usuário do cliente usa uma área de texto simples para inserir uma lista de números separados por vírgula.
const destinations = source
.split(‘,’)
.map((num) => num.trim())
.filter(Boolean);
Uma estrutura de fila processa as chamadas uma a uma. As chamadas não atendidas ou ocupadas podem ser enfileiradas para rediscagem.
destinations.forEach((destNumber) => this.callQueue.enqueue(destNumber));
Lógica de Chamada
A função abaixo recupera o primeiro número da fila e inicia o processamento.
public async makeCallsToDst() {
if (this.callQueue.isEmpty()) return;
const destNumber = this.callQueue.dequeue();
// …
Antes de discar, o sistema verifica a conexão do PABX e garante que o ramal de origem não esteja em uso.
if (!this.sourceDn || !this.externalApiSvc.connected) {
if (destNumber)
this.failedCalls.push({
callerId: destNumber,
reason: NO_SOURCE_OR_DISCONNECTED,
});
return;
}
const participants = this.getParticipantsOfDn(this.sourceDn);
if (participants && participants.size > 0) {
if (destNumber)
this.failedCalls.push({
callerId: destNumber,
reason: CAMPAIGN_SOURCE_BUSY,
});
return;
}
//…
Como Fazer Uma Chamada
A chamada é feita usando o primeiro dispositivo disponível.
É possível encontrar a lista de dispositivos disponíveis para um DN específico dentro do Estado do Controle de Chamadas.
try {
const source = this.fullInfo?.callcontrol.get(this.sourceDn);
const device: DNDevice | undefined = source?.devices?.values().next().value;
if (!device?.device_id) {
throw new BadRequest(‘Devices not found’);
}
const response = await this.externalApiSvc.makeCallFromDevice(
this.sourceDn,
encodeURIComponent(device.device_id),
destNumber,
);
//…
O método makeCallFromDevice usa esse endpoint:
public makeCallFromDevice(source: string, deviceId: string, dest: string) {
const url = ‘/callcontrol’ + `/${source}` + ‘/devices’ + `/${deviceId}` + ‘/makecall’;
return this.fetch!.post(
url,
{
destination: dest,
},
{
headers: {
‘Content-Type’: ‘application/json; charset=utf-8’,
},
},
);
}
Tratamento de Erros
Se o PABX aceitar a solicitação, a ID da chamada será armazenada. Caso contrário, um erro será registrado.
if (response.data.result?.id) {
this.incomingCallsParticipants.set(response.data.result.id, response.data.result);
} else {
this.failedCalls.push({
callerId: destNumber!,
reason: response?.data?.reasontext || UNKNOWN_CALL_ERROR,
});
}
//…
Os erros entre a aplicação e o PABX são tratados aqui:
//…
} catch (error: unknown) {
if (axios.isAxiosError(error)) {
this.failedCalls.push({
callerId: destNumber!,
reason: error.response?.data.reasontext || UNKNOWN_CALL_ERROR,
});
} else {
this.failedCalls.push({
callerId: destNumber!,
reason: UNKNOWN_CALL_ERROR,
});
}
}
Tratamento de Eventos do Participante
Uma conexão WebSocket rastreia o status da URA, inicia novas chamadas e gerencia os participantes.
Você pode encontrar mais detalhes sobre a estrutura de eventos do WebSocket e outros aspectos relacionados neste guia.
private wsEventHandler = (json: string) => {
try {
const wsEvent: WSEvent = JSON.parse(json);
if (!this.externalApiSvc.connected || !wsEvent?.event?.entity) {
return;
}
const { dn, type } = determineOperation(wsEvent.event.entity);
//…
Quando ocorre uma atualização, o aplicativo busca e armazena novos dados.
case EventType.Upset:
{
this.externalApiSvc
.requestUpdatedEntityFromWebhookEvent(wsEvent)
.then((res) => {
const data = res.data;
set(this.fullInfo, wsEvent.event.entity, data); // update local state
if (dn === this.sourceDn) {
if (type === PARTICIPANT_TYPE_UPDATE) {
/**
* handle here update of participants
*/
}
}
})
.catch((err) => {
if (axios.isAxiosError(err)) {
console.error(`AXIOS ERROR code: ${err.response?.status}`);
} else console.error(‘Unknown error’, err);
});
}
break;
Podemos usar esse URL para solicitar a entidade atualizada e executar uma atualização de estado incremental para nosso aplicativo (verifique DN Update Request):
public requestUpdatedEntityFromWebhookEvent(ws: WSEvent) {
return this.fetch.get(ws.event.entity);
}
Quando um participante é removido, a campanha continua.
case EventType.Remove: {
const removed = set(this.fullInfo, wsEvent.event.entity, undefined);
if (dn === this.sourceDn) { // update related to our campaign handler
if (type === PARTICIPANT_TYPE_UPDATE) {// update related to call participant
/**
* handle here removed participants
*/
if (removed?.id) {
//…
if (!participants || participants?.size < 1) { // Handler is free
this.makeCallsToDst(); // continue with campaign
}
}
}
}
}
Podemos usar esse manipulador de eventos no ouvinte de eventos do WebSocket.
ws.on(‘message’, (buffer) => {
const message = decoder.decode(buffer as Buffer);
wsEventHandler(message);
});
Mais Scripts de Fluxo de Chamadas Disponíveis
Temos uma coleção de scripts de fluxo de chamadas em nosso site. Dê uma olhada neles e veja como você pode automatizar o 3CX para atender às suas necessidades.



