What is the best way to connect freepbx-17 to my own custom program.
My goal:
If a user dial an extension like 300, then freepbx-17 connects the call to my own program (like a python script). The python script is already connected to an AI/ML backend. I am looking to interface my program to freepbx-17. What options do I have?
You might consider getting under the hood of FreePBX and down in to testing out the new Media Over Websockets implementation (chan_websocket) in Asterisk - here’s a few code examples:
Thanks for the pointer. Do I need a specific version of Asterisk to try those examples?
I was trying to run (mow_echo_test_server.py):
Terminal 1:
/etc/asterisk/asterisk-websocket-examples# ./mow_echo_test_server.py
[2025-06-28 23:05:24] server listening on 127.0.0.1:8787
[2025-06-28 23:05:24] server listening on [::1]:8787
Terminal 2:
/etc/asterisk# asterisk -r
Asterisk 22.4.1, Copyright (C) 1999 - 2025, Sangoma Technologies Corporation and others.
Created by Mark Spencer <[email protected]>
Asterisk comes with ABSOLUTELY NO WARRANTY; type 'core show warranty' for details.
This is free software, with components licensed under the GNU General Public
License version 2 and other licenses; you are welcome to redistribute it under
certain conditions. Type 'core show license' for details.
=========================================================================
Connected to Asterisk 22.4.1 currently running on FreePBX17 (pid = 1891)
FreePBX17*CLI> channel originate WebSocket/media_connection1/c(ulaw) extension echo@default
[2025-06-28 23:06:45] WARNING[216708]: channel.c:6262 request_channel: No channel type registered for 'WebSocket'
[2025-06-28 23:07:00] NOTICE[1939]: res_pjsip/pjsip_transport_management.c:170 idle_sched_cb: Shutting down transport 'TCP to 150.107.38.40:58868' since no request was received in 32 seconds
[2025-06-28 23:07:10] WARNING[1931]: pjproject: <?>: sip_parser.c Field Content-Length not found!
[2025-06-28 23:07:10] ERROR[1931]: pjproject: <?>: sip_endpoint.c Error processing packet from 150.107.38.40:59656: Missing required header(s) (PJSIP_EMISSINGHDR) CSeq [code 171050]:
GET / HTTP/1.1
Host: 76.14.48.209:5060
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 Edg/120.0.0.0
in one of my installs I made a more generic version of this that hooks calls into a Dynamic Route. These Dynamic routes are quite powerful, you can call code from them and this way I can manage more things from the UI, even if it all starts with a bit of dialplan.
I don’t have much time to explain, but let me dump my stuff here for you (there are bits of internal documentation there…), you’ll actually see a number of techniques there to call code:
[macro-dialout-one-predial-hook]
; Temporary disable:
;exten => s,1,MacroExit
;this is called from /etc/asterisk/extensions_additional.conf, check other available vars there
exten => s,1,Noop(Entering context macro-dialout-one-predial-hook in extensions_custom.conf)
;dump all available vars for debugging:
;exten => s,n,DumpChan
exten => s,n,NoOp(Running AGI for ${EXTEN})
; subroutine to call AGI to check accesses
exten => s,n,Noop(------------------------------------------------------- pgr-checker ------)
exten => s,n,AGI(pgr-checker.php)
exten => s,n,GotoIf($["${PGR_ACCESS_GRANTED}" = "false"]?no_access)
exten => s,n(access),NoOp(Access group matched: CALL ALLOWED)
exten => s,n,Verbose(CALLERID ${CALLERID(num)} is calling ${CALLERID(dnid)})
; Send to Dynamic Route Pgr
exten => s,n,Gosub(dynroute-7,s,1)
; subroutine to call AGI to send notifications
exten => s,n,DumpChan
exten => s,n,Noop(-------------------------------------------------------- sendsms4 --------)
exten => s,n,Set(PGR_ARG_CALLTYPE=starting) ; Send the channel variable to the AGI
exten => s,n,AGI(pgr-sendsms4.php)
exten => s,n(macro_clean_exit),MacroExit
exten => s,n(no_access),NoOp(No access exemptions or groups matched: CALL BLOCKED)
exten => s,n,Playback(silence/1&cannot-complete-as-dialed&check-number-dial-again,noanswer)
exten => s,n,Hangup()
[custom-dynroute-return]
; set up the Dynamic Route to end in a Admin/Custom Destination
; (don't confuse this with Applications/Misc Destination!)
; set up the Custom Destination to target custom-dynroute-return,s,1
exten => s,1,NoOp(Returning call to original flow)
exten => s,n,Return() ; Return to the calling point in [macro-dialout-one-predial-hook]
This might be buggy and might not work for all cases, but it’s a start.