Start eenvoudig met een outbound prompt-campagne.

Met de 3CX Call Control API kun je eenvoudig outbound belcampagnes automatiseren. Verbind een lijst met telefoonnummers aan een IVR, speel een automatisch bericht af en stuur de oproep door op basis van de gemaakte keuze in het keuzemenu. In tegenstelling tot traditionele outbound dialing, biedt deze methode meer flexibiliteit in boodschap, routering én integratie met je CRM of database. Lees verder om te ontdekken hoe je hiermee aan de gang gaat.

Wanneer gebruik je de Outbound Prompt Campagne?

Outbound calls automatiseren

Een goed voorbeeld is een geannuleerde vlucht. Een luchtvaartmaatschappij kan passagiers automatisch op de hoogte brengen via een ingesproken bericht en ze met één druk op de knop doorverbinden met de klantenservice.

Dit is een eenvoudige toepassing, maar je kunt het uitbreiden met een gepersonaliseerd IVR-menu, DTMF-inputverwerking en controle over de audiostream.

Bekijk ook de officiële 3CX GitHub-repository voor meer voorbeelden.

Opzet van de oproepverwerking & API-integratie

Maak een IVR aan in 3CX, geef toegang tot de Call Control API en selecteer de extensie vanuit de lijst.

Oproepen starten

In de gebruikersinterface voer je eenvoudig een lijst met telefoonnummers in, gescheiden door komma’s:

const destinations = source
.split(‘,’)
.map((num) => num.trim())
.filter(Boolean);

Een wachtrij verwerkt de oproepen één voor één. Niet beantwoorde of bezette oproepen kunnen opnieuw worden geprobeerd.

estinations.forEach((destNumber) => this.callQueue.enqueue(destNumber));

Bel logic

Onderstaande functie haalt het eerste nummer uit de wachtrij en start het belproces:

public async makeCallsToDst() {
if (this.callQueue.isEmpty()) return;

const destNumber = this.callQueue.dequeue();
// …

Het systeem controleert vooraf of de PBX-verbinding actief is en of de gebruikte extensie beschikbaar is:

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

//…

Bellen

Er wordt gebeld via via het eerste beschikbare toestel.

De lijst met toestellen voor een DN kun je opvragen via de State of the Call Control.

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,
);
//…

De makeCallFromDevice-methode gebruikt deze 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’,
},
},
);
}

Foutafhandeling

Wanneer de PBX de aanvraag accepteert, wordt het call ID opgeslagen. Anders wordt de fout gelogd:

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,
});
}
//…

Fouten tussen de applicatie en de PBX worden hier afgehandeld:

//…
} 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,
});
}
}

Event Handling van Deelnemers

Een WebSocket-verbinding houdt de status van de IVR bij, start nieuwe oproepen en beheert deelnemers.

Meer over de WebSocket-structuur vind je in deze handleiding.

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);
//…

Bij wijzigingen wordt nieuwe data opgehaald en lokaal opgeslagen.

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;

We kunnen deze URL gebruiken om de bijgewerkte entiteit op te vragen en een statusupdate voor onze applicatie uit te voeren (zie DN Update Request).

public requestUpdatedEntityFromWebhookEvent(ws: WSEvent) {
return this.fetch.get(ws.event.entity);
}

Wanneer een deelnemer is verwijderd, gaat de campagne weer verder.

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

We kunnen deze event handler binnen de WebSocket-listener gebruiken.

ws.on(‘message’, (buffer) => {
const message = decoder.decode(buffer as Buffer);
wsEventHandler(message);
});

Meer scripts beschikbaar

We hebben een verzameling call flow scripts beschikbaar op onze website. Ontdek hoe jij 3CX kunt automatiseren om het maximale uit je communicatie te halen.