Tutorial - Jitsi / Jigasi & FreePBX integration. Along with Asterisk IVR to use Jitsi conference mapper API

For this tutorial , I’m starting from the basis that you have already setup Jitsi-meet , but have not yet installed Jigasi. Also, that you have a separate instance running FreePBX (setup with a DID from a Voip provider) and can receive an inbound call, place an outbound call and that audio works both ways.

If you have audio issues or dropped calls (just using softphone… no jigasi yet) , you need to clear that up first.

Current Specs for reference -

Jitsi Server
• OS: Ubuntu Server 20.04
• Hardware: Quad-Core 2.4Ghz , 16GB DDR3, 120GB SSD

FreePBX Server
• OS: FreePBX Official SNG image: FreePBX 14 • Linux 7.6 • Asterisk 16
• Hardware: Quad-Core 2.4Ghz , 4GB DDR3, 32GB SSD

1. FreePBX - Create an extension to use with Jigasi

• FreePBX > Applications > Extensions
• +Add Extension
• Add new CHAN_SIP Extension
• User Extension: Pick an unused number (I’m using 888)
• Display Name: Call it whatever you want (I’m using Jitsi)
• Secret: Copy this, you’ll need it for Jigasi later

Pro Tip: Pause here. Register this extension with a soft phone. Direct your Inbound Route to send calls to this new extension and make sure inbound/outbound calls work. Once confirmed, remove the extension from your softphone and proceed.

2. Jitsi Server – Install Jigasi

• From terminal:

sudo apt-get install jigasi -y

• When prompted, enter your SIP username. This will be the Extension@your-freepbx-domain
Example: 888@voip.domain.com

• Password will be the ‘secret’ copied from when you created the extension in step 1.

3. Add some SIP related configuration settings to Jigasi

• From terminal:

sudo nano /etc/jitsi/jigasi/sip-communicator.properties

• Add the following lines under the SERVER_ADDRESS line:
Be sure to change domain and ports to match your configuration

net.java.sip.communicator.impl.protocol.sip.acc1403273890647.SERVER_PORT=5160
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PROXY_ADDRESS=voip.domain.com
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PROXY_PORT=5160
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PROXY_ADDRESS_VALIDATED=true
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PROXY_AUTO_CONFIG=false
net.java.sip.communicator.impl.protocol.sip.acc1403273890647.PREFERRED_TRANSPORT=udp

• Add or un-comment the following line:

net.java.sip.communicator.service.gui.ALWAYS_TRUST_MODE_ENABLED=true

4. Add credentials for Jigasi

If you’re using authentication, Jigasi will need it’s own credentials.

• I’m using prosody. prosodyctl register [username] [Jitsi Domain] [Password]

sudo prosodyctl register jigasiadmin meet.domain.com mysuperstrongpassword

• Add the new credentials to /etc/jitsi/jigasi/sip-communicator.properties
• From terminal:

sudo nano /etc/jitsi/jigasi/sip-communicator.properties

• Add the following lines, replacing with the credentials you just created:
Note: password is clear text

org.jitsi.jigasi.xmpp.acc.USER_ID=jigasiadmin@meet.domain.com
org.jitsi.jigasi.xmpp.acc.PASS=mysuperstrongpassword
org.jitsi.jigasi.xmpp.acc.ANONYMOUS_AUTH=false

• Restart Jigasi with the following terminal command

sudo systemctl restart jigasi

5. Pause here and do some testing

At this point, Jigasi’s SIP extension should be registered, let’s validate that by heading over to FreePBX…

• FreePBX > Admin > Asterisk CLI
• Run CLI command: SIP show Peers
• The extension should show “OK” if registered properly

Dialing the extension from another SIP endpoint (desk phone or softphone) should route you to the default Jitsi Room “siptest”

• Pull up a jitsi meeting via web browser , use the name: siptest
• from another registered endpoint, dial your jitsi extension (my example is 888)
• If this doesn’t work , check logs
Jigasi log - (sudo nano /var/log/jitsi/jigasi.log)
FreePBX – Reports > Asterisk logfiles

Only Proceed to step 6 if you can successfully dial into the siptest room

6. Provision Jitsi Dial in feature
Inorder for the Dial-in box to populate, Jitsi requires hosting a JSON file containing the phone numbers. I will be using the FreePBX server for this.

• Create a file titled jitsi_numbers.json, mirror contents of the file below

{"message":"Phone numbers available.","numbers":{"US":["+1.800.123.1234"],"US Alternate":["+1.888.123.1234"]},"numbersEnabled":true}

• Create another file titled jitsiNumberList.php, mirror contents of the file below

<?php
    header('Content-Type: application/json');
    readfile("./jitsi_numbers.json");
    ?>

• Place both files on the FreePBX server under /var/www/html
I use Filezilla for linux to transfer files via ftp

• Enable CORS in Apache. On the FreePBX server, Run terminal command:

sudo nano /etc/httpd/conf/httpd.conf

• Locate and add an Access-Control-Allow-Origin header
replace “https://meet.domain.com” with your jitsi meet domain.
Should look like this:

<Directory />
    Header set Access-Control-Allow-Origin "https://meet.domain.com"
    AllowOverride none
    Require all denied
</Directory>

• Restart httpd

service httpd restart

7. Jitsi – Edit config.js

• Provide Jitsi with the locations of our phone list and API to use
• From Terminal:

sudo nano /etc/jitsi/meet/meet.domain.com-config.js

• Add the following lines.
First line, swap out “ voip.domain.com” with your FreePBX FQDN or IP
Second line is using Jitsi’s API

dialInNumbersUrl: 'https://voip.domain.com/jitsiNumberList.php',
dialInConfCodeUrl: 'https://jitsi-api.jitsi.net/conferenceMapper', 

• At this point, joining a web-meeting should now show the share box populated with your dial-in details

8. FreePBX – Custom IVR
Now for the mystical PBX magic. We need to be able to collect the meeting pin from callers, send that pin off to an API in order to retrieve the conference room name. Then, add the conference room name in the form of a SIP header so that Jigasi knows where we want to go… and ideally, provision password entry and some other IVR-ish bells and whistles.

To begin prepping the IVR-ish application, we need to create a custom destination.
• FreePBX > Admin > Custom Destination
• Target: Jitsi-Conference-Entry,s,1

image

Next, we’ll use the custom destination with an inbound route
• Note: This assumes that you already have a trunk setup for inbound calling
• FreePBX > Connectivity > Inbound Routes
• DID: add the DID callers will dial to reach your conference
you should have already tested inbound calling with an extension, in which case, we’re just setting the custom destination instead of an extension destination

• Set Destination: This will be the custom destination we created above

Lastly, we’ll add some Dial Plan magic

• FreePBX > Config Edit > extensions_custom.conf

• paste in the below dial plan code
• change the 888 to whatever you’re using as your Jitsi SIP extention
• exten => s,2,Set(Jitsi=888)

[Jitsi-Conference-Entry]
exten => s,1,Answer()
;Set the extension used for Jitsi
exten => s,2,Set(Jitsi=888)
;set variable to prevent looping
exten => s,3,Set(Attempts=0)
exten => s,4,Set(Attempts=${MATH(${Attempts}+1,i)})
;Test for invalid entries. On 4th attempt go to error sub
exten => s,5,ExecIf($["${Attempts}" = "4"]?Gosub(Attempts-Error,s,1))
exten => s,6(begin),NooP()
;system listens for DTMF and sets variable "confid"
;10=MAX DIGITS, 10=timeout
exten => s,n,Read(confid,conf-getpin,10,,,10)
;If blank value, start over
exten => s,n,ExecIf($["${confid}"=""]?goto(Jitsi-Conference-Entry,s,4))
;systems plays back the number entered
exten => s,n,Playback(you-entered)
exten => s,n,SayDigits(${confid})
;system asks to press 1 to accept or 2 to retry
exten => s,n,Read(digi,if-this-is-correct-press&digits/1&otherwise-press&digits/2,1,,1,10)
;If blank value, start over
exten => s,n,ExecIf($["${digi}"=""]?goto(Jitsi-Conference-Entry,s,4))
;if user presses 1 to confirm, system moves onto to check for passcode, if applicable
exten => s,n,ExecIf($["${digi}"="1"]?goto(passcode))
;if callers presses any other digit, system will re-ask them to enter in their number
exten => s,n,goto(Jitsi-Conference-Entry,s,begin)
;CURL Jitsi API with meeting pin & retrieve meeting name as the result 
exten => s,n(passcode),Set(CURL_RESULT=${SHELL(curl --silent https://jitsi-api.jitsi.net/conferenceMapper?id=${confid} | sed -e 's/.*"conference":"\(.*\)@.*/\1/')})
exten => s,n,Verbose(0, ${CURL_RESULT});
;speeding this up for the password, but you could mirror the process above if you want the extra readout and verification...
;system listens for DTMF and sets variable "confpin"
;6=MAX DIGITS, 10=timeout
exten => s,n,Read(confpin,pls-enter-conf-password&vm-then-pound&vm-tocancel,6,,,10)
;pls-enter-conf-password
;User will be sent onto the conference whether confpin is blank or not
exten => s,n,goto(enterconf)
;Add SIP Headers based on caller's entries & CURL result
exten => s,n(enterconf),SIPAddHeader(Jitsi-Conference-Room:${CURL_RESULT})
exten => s,n,SIPAddHeader(Jitsi-Conference-Room-Pass:${confpin})
;Sets CDR "userfield" with the Conference ID
;CDR can now be used to track number of calls and durations associated to the Conference ID
exten => s,n,Set(CDR(userfield)=Jitsi:${CURL_RESULT})
;Record Caller's Name
exten => s,n,Set(__rnum=${RAND()})
exten => s,n,Playback(vm-rec-name)
exten => s,n,Record(/tmp/name-${rnum}.gsm,3,10)
;set spygroup to be used for injecting whisper
exten => s,n,Set(SPYGROUP=1000)
;Dial Jitsi extension and play recorded name to the jitisi conference channel
;3 = seconds to ring , m = play music on hold , A = announcement for dialed channel, M = Macro after call connects
exten => s,n,Dial(SIP/${Jitsi},3,m(silence)A(/tmp/name-${rnum})M(Jitsi-join))
exten => s,n,Verbose(0, Contacting ${Jitsi}... Status is ${DIALSTATUS} );
;Take actions based on dialstatus 
exten => s,n,GotoIf($["${DIALSTATUS}" = "BUSY"]?unknown)
exten => s,n,GotoIf($["${DIALSTATUS}" = "NOANSWER"]?conf-busy)
exten => s,n,GotoIf($["${DIALSTATUS}" = "CANCEL"]?unknown)
exten => s,n,GotoIf($["${DIALSTATUS}" = "CONGESTION"]?unknown)
exten => s,n,GotoIf($["${DIALSTATUS}" = "CHANUNAVAIL"]?unknown)
exten => s,n,GotoIf($["${DIALSTATUS}" = "DONTCALL"]?unknown)
exten => s,n,GotoIf($["${DIALSTATUS}" = "TORTURE"]?unknown)
exten => s,n,GotoIf($["${DIALSTATUS}" = "INVALIDARGS"]?unknown)
;Hangup if condition is not matched (in the event a new dialstatus is added with an update
exten => s,n,Hangup()
;Jitsi is not reachable, play error message
;This is also a good place to send an SMS to your lead system admins
exten => s,n(unknown),playback(please-contact-tech-supt&vm-goodbye)
exten => s,n,Hangup()
;Initial attempt was ok, but Jitsi didn't pickup the call. 
;It's likely that the host hasn't yet authenticated / started the meeting
;Indicate that we're waiting for the leader to join and keep trying
exten => s,n(conf-busy),Playback(conf-waitforleader)
;120 = seconds to ring , m = play music on hold , A = announcement for dialed channel, M = Macro after call connects
exten => s,n,Dial(SIP/${Jitsi},120,m(default)A(/tmp/name-${rnum})M(Jitsi-join))
exten => s,n,Hangup()

[Attempts-Error]
;system plays message and hangs up
exten => s,1,playback(sorry-youre-having-problems)
exten => s,n,playback(tt-monkeys)
;exten => s,n,playback(hangup-try-again)
exten => s,n,Hangup()

[macro-Jitsi-join]
exten => s,1,Originate(Local/999@JitsiWhisper,app,Playback,confbridge-join)

[JitsiWhisper]
exten => _X.,1,NoCDR
exten => _X.,n,Answer()
exten => _X.,n,ChanSpy(,g(1000),qw)
exten => _X.,n,Hangup()

  1. Complete!

You should now have a fully functional IVR , along with dial-in options displayed in Jitsi.

Notes:

• IVR – all system recordings used are included with a standard FreePBX 14 install with asterisk 16. If you’re using something else, you may need to swap out some recording names for compatibility.

• IVR - the line below uses an MOH (music on hold group) called ‘silence’ to override the first ring attempt and just play nothing. You won’t have this by default, but it’s ok… the system will use your default MOH in it’s place. If you want to use this, create an MOH group call Silence and place a blank 5 or 10 second recording in it.

exten => s,n,Dial(SIP/${Jitsi},3,m(silence)A(/tmp/name-${rnum})M(Jitsi-join))
10 Likes

This post is absolutely awesome. Well written. Thanks very much.

I have run into a problem.
you reference: https://jitsi-api.jitsi.net/conferenceMapper?id=${confid}

This is hosted outside of our private server? I cannot find any application like this on my server.
If we are supposed to use jitsi’s server for this, how does the “mapping” get populated on their server?

I need to host this locally, as I would expect most people do. Any thoughts?

Per step 7, you’re telling your self-hosted Jitsi server what API to use:

dialInConfCodeUrl: 'https://jitsi-api.jitsi.net/conferenceMapper', 

I’d like to self-host the API as well.
Stay tuned over on the thread here: How can we self-host the ConferenceMapper API?

That said, you could ditch use of thier API and simply stick to numeric meeting names: IVR - no reliance on ConferenceMapper API

Thank you for the explanation.
I have almost everything working now, calls can be placed into the system.
The only thing that is missing is the dial-in numbers box. The numbers do not show up in meetings.
I have made the php page for the number list and tested that it returns the json payload. I can see on my web server log that it IS being requested. However, back in jitsi meet, no numbers are displayed.

If the dialNumbersURL is correct in your config.js & you can see the request, I would explore firewall on the system hosting the php file and also that CORS is really working.

For CORS, you could test the header with a wild card on the off chance something is misconfigured. It’s fine for troubleshooting, I just wouldn’t leave the wild card in place for a production server. Especially if it’s serving external resources… leaving yourself open to exploits.

Header set Access-Control-Allow-Origin "*"

Right, but I proved it works by using curl on the meet server to request the php. It worked. So we can eliminate firewall and web server problems.

You were right, I misunderstood the CORS configuration.
Thanks for all your time! It is appreciated.

Do you mind sharing what the mistake was?
I am trying to get the dial-in pop up box as well but I don’t get it working.

To enable CORS, I added the Header Access Control line into my apache.conf file where starts. My php and json file are on the jitsi server. I can also call them from the www.
I do not see any error in my apache log.

Any ideas?

Thanks for the tutorial!

When I dial the number and enter the conference PIN, it loops me into please enter your conference pin. Any ideas on why it might do that?

Same Problem here!

Your asterisk system is possibly not recognizing the DTMF. The IVR will loop back and ask for the PIN again if the entry from the user is blank (no input), timeout is 10 seconds.

To see if this is the case…

  • Enable DTMF logging:
    FreePBX > Settings > Asterisk Log File Settings > Log Files tab. Turn on “DTMF” for the Full log.

  • Test the IVR again and then check the asterisk log.
    FreePBX > Reports > Asterisk Log files.

  • Look for DTMF lines (or the absence of).
    My example log below shows functioning DTMF detection, with the entry being 123#

  • Note: I would also test external to IVR and internal to IVR. By internal, I mean SIP extension to SIP extension - do not dial the external DID. If it’s only external, could be a provider issue.

VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:1] Answer("SIP/6580-000000c0", "") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:2] Set("SIP/6580-000000c0", "Jitsi=888") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:3] Set("SIP/6580-000000c0", "Attempts=0") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:4] Set("SIP/6580-000000c0", "Attempts=1") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:5] ExecIf("SIP/6580-000000c0", "0?Gosub(Attempts-Error,s,1)") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:6] NoOp("SIP/6580-000000c0", "") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:7] Read("SIP/6580-000000c0", "confid,conf-getpin,10,,,10") in new stack
VERBOSE[16902][C-00000067] app_read.c: Accepting a maximum of 10 digits.
VERBOSE[16902][C-00000067] file.c: <SIP/6580-000000c0> Playing 'conf-getpin.ulaw' (language 'en')
DTMF[16902][C-00000067] channel.c: DTMF begin '1' received on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin ignored '1' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF end '1' received on SIP/6580-000000c0, duration 160 ms
DTMF[16902][C-00000067] channel.c: DTMF end passthrough '1' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin '2' received on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin ignored '2' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF end '2' received on SIP/6580-000000c0, duration 160 ms
DTMF[16902][C-00000067] channel.c: DTMF end passthrough '2' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin '3' received on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin ignored '3' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF end '3' received on SIP/6580-000000c0, duration 160 ms
DTMF[16902][C-00000067] channel.c: DTMF end passthrough '3' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin '#' received on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF begin ignored '#' on SIP/6580-000000c0
DTMF[16902][C-00000067] channel.c: DTMF end '#' received on SIP/6580-000000c0, duration 160 ms
DTMF[16902][C-00000067] channel.c: DTMF end passthrough '#' on SIP/6580-000000c0
VERBOSE[16902][C-00000067] app_read.c: User entered '123'
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:8] ExecIf("SIP/6580-000000c0", "0?goto(Jitsi-Conference-Entry,s,4)") in new stack
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:9] Playback("SIP/6580-000000c0", "you-entered") in new stack
VERBOSE[16902][C-00000067] file.c: <SIP/6580-000000c0> Playing 'you-entered.ulaw' (language 'en')
VERBOSE[16902][C-00000067] pbx.c: Executing [s@Jitsi-Conference-Entry:10] SayDigits("SIP/6580-000000c0", "123") in new stack

I had that problem too, if I registered the extension as pjsip and not chan_sip. Chan_sip worked just as Craig described in the tutorial.

1 Like

Could you clarify? See my post below yours.

I host the numbers.json file on an existing web server.
On that server I added a header:

Access-Control-Allow-Origin

And it is set to: “https://meet.aquilatech.com

That is necessary for your web browser to trust the data coming from two places.

Hi,
Could you please tell me in which file and which -line you have added that command?
The domain you add in there is your general jitsi domain, right?
Maybe also what are the permission of users on the PHP and json file? Do I need any special application to host .json-files?

That’s going to be unique to your network. In my case, I have a web server with dozens of web sites on it. So I put this on a site I use for other systems, for the same kinds of things. In this case it is Windows IIS server. You can put the file on any web site.

Do you have a running web server on your network already?

Yes, everything is running already. I try to put the Header Access Origin line into the virtual host configuration of the subdomain which hosts the json and PHP file. I am running an apache webserver.

From your web browser you should be able to request the json data manually. Put the exact URL you are putting into the dialInNumbersUrl

If that works, then temporarily set your Access-Control-Allow-Origin to “*” on that web. I bet when you that it starts working.

1 Like

Hi,
I found the mistake. I was serving the PHP and json file over the HTTP Protocol (Port 80). Serving it over port 443 (HTTPS) fixed the problem.

1 Like