Automatischen Anruf / CallFlow triggern

Univention IT

Customer
Mitglied seit
10. November 2025
Beiträge
9
Hallo,

ich versuche (wie früher mit Asterisk) einen Anruf zu starten der beim Verbindungsaufbau ein Audiofile (dynamisch generierte Ansage) abspielt.
Hintergrund: automatisierte Alarmierung.

Ein Bekannter meinte das klappt auch mit 3CX. Ich konnte über den CFD ein Script erzeugen das genau das machen würde (?) was ich möchte:
1762789140659.png
Da es kein Wait/Sleep als Komponente gibt habe ich eine UserDefinedComponent mit einem C# Schnipsel erzeugt.

Jetzt suche ich nur eine Möglichkeit dies auch zu triggern. Über keine der beiden APIs (Call Controll oder Configuration) konnte ich jedoch einen geeigneten Endpoint finden.

Wenn ein Support-Ticket eröffnet wird wird aus den Informationen eine Ansage generiert die bereits wichtige Infos enthält. Diese sollen dann mit einem Alarmierungs-Anruf übermittelt werden und werden dazu per https bereitgestellt. Das Script soll dann - wenn getriggert - den Anruf an eine bestimmte Nummer starten und bei erfolgter Verbindung die Ansage (de und en) jeweils 3x abspielen und dann auflegen. Kommt keine Verbindung zustande wird es nach kurzer Zeit (bis zu X mal) erneut versucht.

Vielen Dank für jeden Hinweis und/oder Hilfe!
 
Nachtrag:
alle Parameter im CallFlow werden über Variablen, die ich per API (oder irgendwie anders) übergeben möchte, gefüllt.
1762790602767.png
Dadurch kann (weiterhin) alles über das bereits existierende Alarmierungs-Script gesteuert werden. Außerdem könnte es parallele Tickets geben so das 'de_audio' und 'en_audio' ohnehin dynamisch sein müssen; beim Rest könnte man auch konstante Werte verwenden.

Nochmal Danke für jede Unterstützung!
 
Hallo @Univention IT
Eine Frage vorneweg: Kann es sein, dass du nicht im 3CX Subforum Call Flow Designer schreiben kannst? Da gehört so etwas grds. hin. Das können aber nicht alle Forenuser einfach so :)

Zur Aufgabe:
Das Bild des Callflow der CFD App sagt zu wenig aus. Die Beschreibung dessen was du willst hilft mir auch nicht wirklich weiter.

Ich denke mir mal meinen Teil dazu:
Dein Skript nimmt den Anruf vom Angerufenen A an, startet in einer Schleife (welche Abbruchbedingung kommt da?) erst ein Makecall mit dem zwei andere (Anrufender B und Angerufener C) verbunden werden, dann spielt es evtl. dem Anrufer A Audio ab und wartet evtl. etwas (das evtl. auch wieder in einer Schleife) und dann starten evtl. gar die Schleife mit dem Makecall zu Anfang erneut. Ich denke das ist nicht der Plan.

Grundsätzlich:
Eine CFD App handhabt genau einen Anruf: den der die CFD App anruft. Dem kann man etwas vorspielen oder auch nicht.

Alternativ kann man mit Makecall auch einen weiteren fremden Anruf aufbauen lassen. Man kann so eine int. NSt. (nachdem die angenommen hat) oder eine andere CFD App (die immer und sofort einen Anruf annimmt) mit einer 'beliebigen' anderen Nummer verbinden lassen. Drück mal im CFD wenn der Fokus auf dem Makecall Code Block steht die Taste F1 oder klick rechts auf den Block drauf und dann auf Help - da kommt die Erklärung des Baustein.

Das was da als Bild in deinem Post steht widerspricht vmtl. deinem Plan:
ich versuche (wie früher mit Asterisk) einen Anruf zu starten der beim Verbindungsaufbau ein Audiofile (dynamisch generierte Ansage) abspielt.
Hintergrund: automatisierte Alarmierung.
Dann spielst du grds. demjenigen der die CFD App anruft etwas ab. Da ist das Makecall - zumindest dort - fraglich.

Wenn du nur den Anruf starten willst der jemand anderen anruft und etwas abspielt, dann sind das grds. zwei CFD Apps:
  1. eine mit dem einzelnen Makecall (die z.B. eine CFD App mit etwas anderem verbindet)
  2. eine die nach Anruf Audio abspielt das hoffentlich von etwas anderem gehört wird
Man kann beide CFD Apps / Anrufe auch in eine einzelne CFD App verpacken. Die CFD App prüft ob sie sich selbst anruft, wenn nein dann macht den den Makecall mit sich selber (Punkt 1), wenn ja dann spielt die das Audio ab (Punkt 2).

Da ist aber noch nicht das Problem angesprochen was denn passiert wenn die zweite CFD App per Makecall startet und niemand zuhört. Das hängt auch von der Art des so Angerufenen ab. Wer oder was ist das?


Nur der Vollständigkeit halber: Es gibt noch Konzept eines Dialers als CFD App, aber das ist sicher nichts für diese Aufgabe.
 
Jetzt suche ich nur eine Möglichkeit dies auch zu triggern. Über keine der beiden APIs (Call Controll oder Configuration) konnte ich jedoch einen geeigneten Endpoint finden.
Da gibt es mehrere völlig unterschiedliche Varianten. Was genau soll denn der Auslöser sein? Drückt da jemand auf eine Taste an einem 3CX Endgerät / ruft die erste CFD App an? Soll das wirklich komplett von außen oder sonstwie durch einen Event gestartet stattfinden?
 
Hallo @fxbastler,

das Ganze soll tatsächlich ohne menschliche Interaktion laufen - zumindest bis zu dem Punkt wo der Anruf, der durch das Script (CFD App?) gestartet wird, von jemanden entgegengenommen wird.

Ich hatte die Komponenten so verstanden das beim Zustandekommen der Verbindung (CallCondition == Success), mit 'PlayBack' ein beliebiges Audio abgespielt werden kann.
Der andere Zweig, wenn keine Verbindung zustande kommt (CallCondion: Else), wartet einfach und startet dann den LoopRetry (höchstens 'max_retries') erneut.

XML:
<?xml version="1.0" encoding="utf-8"?>

<Project Name="247Notify" Type="Dialer" Language="en-US" Version="18.0.5">
  <Parameters>
    <Parameter Name="TargetNumber" Type="String" />
    <Parameter Name="CallerID" Type="String" />
    <Parameter Name="de_audio" Type="String" />
    <Parameter Name="en_audio" Type="String" />
    <Parameter Name="beep_file" Type="String" />
    <Parameter Name="initial_wait" Type="Int32" />
    <Parameter Name="repeat_wait" Type="Int32" />
    <Parameter Name="final_wait" Type="Int32" />
    <Parameter Name="repeat_count" Type="Int32" />
    <Parameter Name="retry_wait" Type="Int32" />
    <Parameter Name="max_retries" Type="Int32" />
  </Parameters>

  <Workflow>
    <Component Name="Start" Type="StartComponent" />
    <Component Name="LoopRetries" Type="LoopComponent">
      <Property Name="LoopCondition">$index < $max_retries</Property>
      <Components>
        <Component Name="MakeCall" Type="MakeCallComponent">
          <Property Name="DestinationNumber">$TargetNumber</Property>
          <Property Name="CallerId">$CallerID</Property>
          <Property Name="VariableName">callResult</Property>
        </Component>

        <Component Name="CheckCall" Type="ConditionComponent">
          <Branches>
            <Branch Name="Success" Expression="$callResult.Status == 'Success'">
              <Component Name="SleepInitial" Type="SleepComponent">
                <Property Name="Timeout">$initial_wait</Property>
              </Component>

              <Component Name="PlayBeepStart" Type="PlayAudioComponent">
                <Property Name="File">$beep_file</Property>
              </Component>

              <Component Name="RepeatLoop" Type="LoopComponent">
                <Property Name="LoopCondition">$repIndex < $repeat_count</Property>
                <Components>
                  <Component Name="Sleep1" Type="SleepComponent">
                    <Property Name="Timeout">$repeat_wait</Property>
                  </Component>
                  <Component Name="PlayDE" Type="PlayAudioComponent">
                    <Property Name="File">$de_audio</Property>
                  </Component>
                  <Component Name="Sleep2" Type="SleepComponent">
                    <Property Name="Timeout">$repeat_wait</Property>
                  </Component>
                  <Component Name="PlayEN" Type="PlayAudioComponent">
                    <Property Name="File">$en_audio</Property>
                  </Component>
                </Components>
              </Component>

              <Component Name="PlayBeepEnd" Type="PlayAudioComponent">
                <Property Name="File">$beep_file</Property>
              </Component>

              <Component Name="SleepFinal" Type="SleepComponent">
                <Property Name="Timeout">$final_wait</Property>
              </Component>

              <Component Name="Hangup" Type="TerminateCallComponent" />
            </Branch>

            <Branch Name="Fail" Expression="$callResult.Status != 'Success'">
              <Component Name="SleepRetry" Type="SleepComponent">
                <Property Name="Timeout">$retry_wait</Property>
              </Component>
            </Branch>
          </Branches>
        </Component>
      </Components>
    </Component>
  </Workflow>
</Project>

Falls das so funktioniert habe ich nur noch das Problem den Anruf zu starten - es ist also keine Reaktion auf einen ankommenden Anruf, sondern eine Aktion die irgendwie angestoßen werden soll. Ich hoffe bisher noch immer, daß ich diesen Anruf (bzw. dieses CallScript) irgendwie triggern kann, z.B. über einen API Aufruf, da das eigentliche Ereignis aus einem Ticketsystem kommt.

Herzlichen Dank soweit!
 
Ich hoffe bisher noch immer, daß ich diesen Anruf (bzw. dieses CallScript) irgendwie triggern kann, z.B. über einen API Aufruf, da das eigentliche Ereignis aus einem Ticketsystem kommt.
Die 3CX Call Control API kann z.B. das Makecall auslösen. Das ist ein 3CX Bordmittel und das ist dokumentiert. Da braucht man zum Einen die richtige Lizenz (mind. 8SC und ENT) und einen eigenen ext. Websocket Server der diese API handhabt. Damit wird Punkt 1 abgehandelt indem z.B. der Endpunkt /callcontrol/{dnnumber}/makecall genutzt wird.
Siehe www.3cx.com/docs/call-control-api/ und https://github.com/3cx/call-control-examples
Das erfordert vmtl. ein klein wenig mehr als erwartet, aber das triggert die 3CX extern.

Alternativ eine ext. Software nutzen die sich per SIP an der 3CX anmeldet (sofern das erlaubt wird) und die startet den initialen Anruf auf Punkt 1.

Alternativ sich auf der 3CX per Powershell (in einer Windows 3CX eingebaut, in einer Debian 3CX in der Grauzone problemlos nachrüstbar) unter Nutzung der 3cxpscomcpp2.dll etwas bauen dass den Makecall ausführt. Das läuft auf Pollen (eher kein Trigger) eines ext. Events hinaus, ist aber wirklich simpel. Vorausgesetzt man hat Zugang zum Betriebssystem der 3CX. So ein Szenario nutzen viele seit Jahren für alles Mögliche und Unmögliche.

Alternativ sich für die 3CX ein .Net Core Programm bauen und dort laufen lassen. Auch das würde die 3cxpscomcpp2.dll nutzen. Auch das funktionier prinzipiell so wie die Powershell Variante.

Alternativ kann man Drittanbietersoftware käuflich erwerben die Teile deiner Aufgabe ermöglichen, Suchstichwort voiptools. Der Matthew hat einen .Net Core Wrapper gebaut der nativ auf der 3CX läuft und den kann man fernsteuern.
 
Zuletzt bearbeitet:
  • Like
Reaktionen: Univention IT
Hallo,

leider komme ich hier nicht weiter.

Ich bekomme den Fehler:
Python:
requests.exceptions.HTTPError: 415 Client Error: Unsupported Media Type for url: https://phone/callcontrol/1040/makecall
Wenn ich über die DID '1040' versuche. Leider ist hierzu weder etwas in der Doku noch im Forum zu finden.

Wie man am Ansatz vielleicht erkennen kann - ansonsten der Vollständigkeit halber: die 3CX läuft auf einem Debian 12 und der Call wird über Python (auf einem Debian basierten System) erzeugt. Wir haben eine Enterprise 8SC Lizenz, die API ist eingerichtet und reagiert und ich bekommen auch ein gültiges Token.

Ich hatte gehofft hier (https://www.3cx.com/docs/call-api-linux/) irgendwelche Infos zu finden aber für meinen Anwendungsfall scheint das irrelevant zu sein.

Hat jemand ein Beispiel wie das funktionieren sollte?

Vielen Dank!
 
Ich bekomme den Fehler:
Python:
requests.exceptions.HTTPError: 415 Client Error: Unsupported Media Type for url: https://phone/callcontrol/1040/makecall
Mit so einer URL kann ich gar nichts anfangen. Ich habe keine Ahnung woher das kommt

Ich hatte gehofft hier (https://www.3cx.com/docs/call-api-linux/) irgendwelche Infos zu finden aber für meinen Anwendungsfall scheint das irrelevant zu sein.
Das ist richtig. Das dort ist in diesem Fall irrelevant. Diese Doku nutzt man wenn z.B. auf der 3CX Programme (aktuell in .NET core 8, sprich C#) entwickelt werden sollen. Hinweis ganz fett dazu:
IMPORTANT: The following is meant to be run in a development environment, not on a production 3CX machine.

Zu deiner Suche nach der Doku - siehe meine Links oben:
Siehe www.3cx.com/docs/call-control-api/ und https://github.com/3cx/call-control-examples
Das erfordert vmtl. ein klein wenig mehr als erwartet, aber das triggert die 3CX extern.
Zu diesen Links kommt man auch über Mehr erfahren wenn man sich den Call Control API Zugang in der 3CX einrichtet.
 
Mit so einer URL kann ich gar nichts anfangen. Ich habe keine Ahnung woher das kommt
Ich hätte auch https://<PHONE-URL>/callcontrol/1440/makecall oder https://phone.selfhost.net/callcontrol/1440/makecall schreiben können, das sollte doch unerheblich sein; es handelt sich um eine on-premise Instanz auf einem Debian 12.

Problem war hier, das der Header keinen 'Content-Type': 'application/json; charset=utf-8' hatte, und es nicht ausreichte, daß das Payload als json übermittelt wurde.

Das habe ich aber gefunden und behoben und nun erhalte ich:
Python:
requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://<PHONE-URL>/callcontrol/1440/makecall

Ich würde aber behaupten, daß das API-Profil die nötigen Berechtigungen hat:
1763575999224.png
 
Siehe dein Bild: du hast keine Extension gewählt. Soweit ich das kenne ist das wenigstens eine Bindung die benötigt wird für Anrufe.

Andersrum: Wie will man einen Anruf starten ohne Anfang? Wenn z.B. mit dem von 3CX gelieferten Beispiel des Dialer ein Anruf ausgeführt wird, dann ist das ein MakeCall von da aus.

Ein MakeCall baut grds. (in der 3CX per Softphone und Nicht-CTI-Endgerät, in einer CFD App ...) einen Anruf von einer 3CX Entität aus auf. Das kann ein beliebiges Endgerät oder eben auch ein Programm auf der 3CX (CFD App, Anrufskript) sein.

Wenn der Initiator des Anrufers (die 3CX Entität) z.B. eine NSt. mit einem CTI Telefon ist, dann startet der Anruf sofort von diesem Telefon aus.
Ist das ein Programm, dann nimmt das auch immer sofort an und das Ziel wird angewählt.
Ist das ein Softphone oder so etwas, dann ruft es zuerst dort am 'Gerät', man nimmt an, dann wird erst der Anruf zum Ziel aufgebaut.
Das ist es was MakeCall in etwa macht.

Das wäre der Plan: baue ein Programm (CFD App oder Anrufskript) das irgend etwas tut, das wird als Extension der API gewählt, das MakeCall per Control API startet, der Anruf startet als Initiator von diesem Programm aus, das Programm nimmt den initialen Anruf immer an und startet den Anruf zum Ziel - was auch immer das ist.

Mit dem 3CX Beispiel des Dialer funktioniert das.

Aber ich gebe zu, ich kenne mich da nicht sehr weit aus. Ich kann das Ganze (den Server mit der Software für diese REST API) z.B. nicht ohne weiteres als Code selber komplett oder aktuell wenigstens in kleinen funktionellen Teilen nachbauen. Ich nehme im Zweifelsfall die Typeskript Beispiele. Daher höre ich i.d.R. an dieser Stelle auf.
Ich sage es immer wieder: Ich bin kein Programmierer :D

Wir brauchen so eine Konstruktion grds. nicht und können das daher anders machen. Wir starten bei Bedarf einen ganz normalen Anruf von einem 'SIP Client' aus der tlw. Audio vorgaukelt, entweder ein steuerbares Endgerät und auch sipcmd ist so eine Möglichkeit. Möglich ist auch ein Skript das bei Bedarf (halblegal) auf der 3CX läuft, sich in die 3cxpscomcpp2.dll einklinkt und das tut.
 
Zuletzt bearbeitet:
Hallo und erstmal ein Danke für die bisherige Unterstützung. Ich habe es jetzt soweit hinbekommen, daß ich über die API den ServicePrincipal (callbot) dazu bekomme einen Anruf abzusetzen der das CallFlow Script startet.

Jetzt versuche ich mich bisher vergeblich daran die Werte, die ich im Payload via `attacheddata` mitgebe, ins Script zu bekommen. Ich hatte das über die Komponente GetAttachedCallData versucht aber der DataValue meiner Komponente ist immer "".
In den Beispielen bin ich leider nicht fündig geworden.

Danke!
 
Danke für die rasche Antwort nur sehe ich (noch) nicht wie mir das hilt.

Ich habe ja meinen API-Aufruf mit seinem Payload der mein CallFlow Script triggert. In diesem ist zur Designzeit ja nichts über irgendwelche Werte im Payload bekannt. Ich dachte daher ich muß diese erst über die folgende Komponente übertragen.1764756132204.png

JSON:
payload = {
    "destination": f"{endpoint}",
    "timeout": 30,
    "attacheddata": {
        "public_CallerID": "callbot",
        "public_DID": f"{did}",
        "public_TargetNumber": f"{mobile}",
        "public_ticketID": f"{tckt_id}",
        "public_de_audio": snd_name.replace('{lang}', 'de'),
        "public_en_audio": snd_name.replace('{lang}', 'en'),
        "public_beep_file": "beep.wav",
        "public_initial_wait": "1",  # seconds
        "public_repeat_wait": "2",  # seconds
        "public_repeats": "3",
        "public_final_wait": "3",  # seconds
        "public_retry_wait": "10",  # seconds
        "public_max_retries": "6",
    },
}

1764756666457.png
Im Log sehe ich dann aber, das er nur eine leere Zeichenkette hat.
LoggerData - CallFlow.CFD.LoggerComponent 'LoggerData' - GetAttachedCallData_max_retries:

Dann versuche ich - hier an einem Loop - meinen Index bis zu max_retries zu wiederholen, wobei max_retries (jetzt als public_max_retries) im Payload übergeben wird.

1764756082797.png

Code:
Callflow - Error executing last component: System.FormatException: The input string '' was not in a correct format.
   at System.Number.ThrowFormatException[TChar](ReadOnlySpan`1 value)
   at System.String.System.IConvertible.ToInt32(IFormatProvider provider)
   at CallFlow.CFD.CFDFunctions.TO_INTEGER(Object o)
   at _1440_12.Main.<>c__DisplayClass22_0.<InitializeComponents>b__3()
   at CallFlow.CFD.LoopComponent.EvaluateCondition()
   at CallFlow.CFD.LoopComponent.Start(TimerManager timerManager, Dictionary`2 variableMap, TempWavFileManager tempWavFileManager, PromptQueue promptQueue)
   at _1440_12.Main.ProcessStart()


Nachtrag: Die Dokumentation zum verwendeten API-Call ist hier zu finden: https://www.3cx.com/docs/call-control-api-endpoints/#h.k8kldqyusu63
 
Zuletzt bearbeitet:
Wenn eigene Daten im CFD (und vmtl. auch über die Call Control API - habe ich noch nicht probiert) an einen Anruf per AttachCallData angehangen und anderswo mit GetAttachedCallData ausgelesen werden sollen, dann muss die benannte Variable mit public_ anfangen, z.B. public_max_retries. Das ist es was ich geschrieben habe:
Setze ein public_ davor ;)

Danke für die rasche Antwort nur sehe ich (noch) nicht wie mir das hilt.
hoffentlich jetzt :)
 
Ja, so hatte ich das schlußendlich auch verstanden und dann umgesetzt.

Nachdem ich mir jetzt nochmal https://www.3cx.com/docs/manual/cfd-components/#h.yr9y1ppx7pog genau angeschaut habe habe ich den Eindrick, daß GetAttachedCallDatanur mit AttachCallData innerhalb des CallFlow Scripts klar kommt. Entweder die Werte aus der API wurden bei der Implementierung vergessen oder das funktioniert nochmal ganz anders und ich kann keine Doku dazu finden... das wäre allerdings etwas frustrierend.

Wenn man das doch nur mit dem Support oder der Entwicklung bei 3CX einmal klären könnte.
 
Nachdem ich mir jetzt nochmal ... angeschaut habe habe ich den Eindrick, daß GetAttachedCallDatanur mit AttachCallData innerhalb des CallFlow Scripts klar kommt.
Nein, auch außerhalb - so lange es derselbe Anruf ist.
Wenn beispielsweise ein 3CX Anrufskript beim ersten Anruf auf dem Trunk triggert und da etwas reinschreibt, der Anruf weiter durch die 3CX läuft und nirgends angenommen wird (RG ohne Ansage, Nutzerweiterleitung bei Nichtannahme funktioniert; CFD App, WS, IVR und sonstige Ansagen funktionieren nicht) dann bleibt es derselbe Anruf und die angehangenen Daten sind abrufbar für den ersten der den Anruf annimmt.

Aber es ist wie immer: selber suchen und finden. So mache ich das immer.

Beispiel: Wir haben Powershell auf der 3CX und ich klinke mich live in die Anlage ein. Dann kann ich alle lfd. Anrufe und auch alle Daten sehen - inkl. eben AttachedCallData, alles live und in Farbe. Das kann ich nur empfehlen. Zum Anfang siehe hier:
https://www.3cx.de/forum/threads/automatischer-statuswechsel-problem.121456/post-439013 - insgesamt ein schöner Thread :)
 
  • Like
Reaktionen: Univention IT
Es gibt ein ähnliche Anfragen im US-Forum, vielleicht gibt es von dort noch einmal Anregungen:
Dazu noch ein Hinweis. Ich habe es bisher so nicht gelesen oder verstanden, dass du mehrere Dinge in mehreren voneinander unabhängigen Variablen an einen Anruf anhängen willst. Das ist für mich neu. Mit dem CFD z.B. geht das nicht. Ich müsste das mit o.g. Mitteln (einklinken in der 3CX) selber eruieren ob das aktuell überhaupt möglich ist - egal ob 'von aussen' per API initiiert oder nicht.
Bisher war es grds. so, dass nur einmalig ein individueller Parameter per AttachCallData an den Call angeheftet werden kann, siehe hier:
https://www.3cx.de/forum/threads/callflow-mehrere-attached-call-datas-führen-zu-problemen.100036/post-331065

Wir haben uns bisher immer damit beholfen, die verschiedenen zu übergebenden Daten geschickt in einen einzelnen Parameter verpackt zu übergeben. Das geht. Wie schon geschrieben: ich weiss nicht ob auch das über die 3CX Call Control API funktionieren kann. Das müsste ich nachbauen.
Wir haben so eine Anbindung in Betrieb aber eben: die ist in Betrieb, die ist produktiv und da pfusche ich mit so etwas für Tests nicht einfach dazwischen. Dafür müsste ich eine vorh. Testinstallation samt Testanbindung reaktivieren und dafür fehlt mir grad die Zeit.
 
Ich hatte diese letzte Antwort heute den ganzen Tag offen und noch nicht verschickt. Wie ich eben im internet. Forum lese wurde dir dort dieselbe Empfehlung - zumindest so ähnlich - gegeben. Ich hätte das schon heute Mittag schreiben können. Das hätte dir vmtl. Zeit gespart - aber ich habe eben auf Arbeit auch andere Dinge zu tun, nicht nur 3CX Forum & Co. ;)
 
  • Like
Reaktionen: Univention IT

Statistik des Forums

Themen
44.411
Beiträge
232.703
Mitglieder
78.328
Neuestes Mitglied
as7h