Tutorial - Self hosted Conference Mapper API

For this tutorial , I’m starting from the basis that you already have Jitsi’s API working with your system and simply want to self host , moving away from dependency on https://jitsi-api.jitsi.net/

First, an overview of how the API works -

When users initiate a conference, their browser sends an http request to the API defined in /etc/jitsi/meet/your.domain-config.js > dialInConfCodeUrl . The request is sent in the following format


The API returns a string that looks like this:

{"message":"Successfully retrieved conference mapping","id":245367,"conference":"newmeeting1234@conference.meet.domain.com"}

Further, when an IVR requests meeting information, a similar request is sent with a known id:


For the ID request, The API returns a string formatted exactly the same as the conference request:

{"message":"Successfully retrieved conference mapping","id":245367,"conference":"newmeeting1234@conference.meet.domain.com"}

Let’s get started!

We will mimic this process using PHP and MySQL . I will be hosting all elements on my Jitsi system , which is running on Ubuntu 20.04 server.

1. Install mySQL

sudo apt-get install mysql-server

  • After install , run the secure install utility

run sudo mysql_secure_installation utility

  • This part will ask security related questions. Personally , I use strong password strength , make a root password, prevent root from remote login, and remove the test table. Just good security practice in my opinion.

  • Optional - Create a remote user to use a GUI on another system

enter the following commands, replacing USERNAME & PASSWORD with your desired credentials:

sudo mysql
grant all privileges on *.* TO 'USERNAME'@'%';
\q (to exit)
  • start mySQL:
sudo systemctl start mysql
  • enable start on boot:
sudo systemctl enable mysql
  • edit bind addresses in mysql config
sudo nano /etc/mysql/mysql.conf.d/mysqld.cnf

ensure you have the following lines ( should already be there)

bind-address =
bind-address = (replace with server's external IP for remote access)
  • restart mysql to pickup the changes
sudo systemctl restart mysql


2. Create mysql table This will house the id & conference name data

I’m using MySQL workbench for the actual table creation, connected using the optional remote user created

  • File > new model > Add Table: jitsiapi

  • Create 2 columns: id & conference, use the following settings for each

[ id ]

Datatype: INT
PK – Primary Key
NN - Not Null
UQ - Unique
A - Autoincrement

[ conference ]

Datatype: VARCHAR(255)
NN - Not Null
UQ - Unique
  • to create the table, Database > forward engineer > Click next through the prompts to write the table

Note: By default , mysql will start inserting id’s sequential from 1 (1, 2, 3, 4). since the ID will be our pin, I’d like to start with something a bit larger. Log into mysql from terminal and run the command below. The number 344100 can be whatever you want to start with.

sudo mysql
ALTER TABLE `mydb`.`jitsiapi` AUTO_INCREMENT=344100;

Mysql tips -

  • You’ll probably want to clear out test data at some point. To quickly clear the table run:
TRUNCATE TABLE `mydb`.`jitsiapi`;
  • clearing the table also resets the starting id , so you’ll want to redo the auto increment command:
ALTER TABLE `mydb`.`jitsiapi` AUTO_INCREMENT=344100;
  • if you receive Access denied for user ‘root’@‘localhost’ , log into mysql from terminal and run following command:
ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'insert_password';

3. Install PHP & add nginx config

sudo apt install php-fpm php-mysql
  • modify nginx site config
sudo nano /etc/nginx/sites-available/your.domain.conf
  • Add the following location to the existing SSL Server block

(change “php7.4” to the version you just installed)

location ~ [^/]\.php(/|$) {
fastcgi_pass unix:/run/php/php7.4-fpm.sock;
include snippets/fastcgi-php.conf;
  • restart nginx
sudo systemctl restart nginx

4. Upload PHP file

I’m hosting php files from the jitsi-meet/static location. To do this, you need a user with write access. I prefer to acl for permissions.

sudo apt install acl
sudo setfacl -m u:Existing-UserName-Here:rwx /usr/share/jitsi-meet/static
  • now you can use a remote user to add php files via FTP (I use filezilla)

  • save the below code as a php file and upload to usr/share/jitsi-meet/static/
    Note: There’s a commented section that restricts the id to 6 digits , change this to suit your needs.

header('Content-Type: application/json');
//header("Content-Type: text/plain");
$conference = $_REQUEST['conference'];
$id = $_REQUEST['id'];

$db_host = 'localhost';
$db_username = 'root';
$db_password = 'PASSWORD';
$db_name = 'mydb';
$db_table = 'jitsiapi';
$digits = ceil(log10($id));

// Set blank variable if conference contains spaces or is missing "@conference."
if (preg_match('/\s/',$conference) > 0) {
    $conference = '';
} else {
    $conference = $conference;
if (strpos($conference, '@conference.') !== false) {
    $conference = $conference;
} else {
    $conference = '';

// Create connection
$conn = new mysqli($db_host, $db_username, $db_password, $db_name);
// Check connection
if ($conn->connect_error) {
    die("Connection failed: " . $conn->connect_error);

//Check that ID is numeric & 6 digits in length
if (is_numeric($id) && $digits = 6) {

$sql = $conn->prepare("SELECT id, conference FROM $db_table WHERE id=?");
$sql->bind_param("i", $id);
$result = $sql->get_result();

if ($result->num_rows > 0) {
  // output data of each row
  while($row = $result->fetch_assoc()) {
     echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}";

} else {
  echo "{\"message\":\"No conference mapping was found\",\"id\":$id,\"conference\":false}";

} else {

$sql = $conn->prepare("SELECT id, conference FROM $db_table WHERE conference=?");
$sql->bind_param("s", $conference);
$result = $sql->get_result();

if ($result->num_rows > 0) {
  while($row = $result->fetch_assoc()) {
 echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}";

} else {

if ($conference == NULL) { 
echo "{\"message\":\"No conference or id provided\",\"conference\":false,\"id\":false}";
} else {

// Insert Conference
$sql = $conn->prepare("INSERT INTO $db_table (conference) VALUES (?)");
$sql->bind_param("s", $conference);
$result = $sql->execute();

// Return resulting data
$sql = $conn->prepare("SELECT id, conference FROM $db_table WHERE conference=?");
$sql->bind_param("s", $conference);
$result = $sql->get_result();

while($row = $result->fetch_assoc()) {
 echo "{\"message\":\"Successfully retrieved conference mapping\",\"id\":" . $row["id"]. ",\"conference\":\"" . $row["conference"]."\"}";

5. Provision config.js and IVR

  • Tell jitsi to use your new php script as the API. Replace meet.domain.com with your jitsi domain
sudo nano /etc/jitsi/meet/meet.domain.com-config.js
  • Modify the dialConfCodeULR line to match your new path
dialInConfCodeUrl: 'https://meet.domain.com/static/conferencemapper.php',
  • IVR - update your existibg curl url to match what you used above.

Note: If you’re coming from my IVR tutorial, here’s a modified curl command. I added a pipe to remove new lines if they exist in the result.

exten => s,n(passcode),Set(CURL_RESULT=${SHELL(curl --silent https://meet.domain.com/static/conferencemapper.php?id=${confid} | sed -e 's/.*"conference":"\(.*\)@.*/\1/' | tr -d "\n")})

  1. Pause here and do some testing

At this point, your API should be functioning. Let’s do some testing…

  • Start a web conference and navigate to the share box, check that a pin as populated.

  • Open another web browser or tab and enter the following, replacing with your domain and PIN obtained from the conference.


This should respond with text indicating the conference name associated with the ID, formatted like the example below:

{"message":"Successfully retrieved conference mapping","id":245367,"conference":"newmeeting1234@conference.meet.domain.com"}
  • Test the IVR with the same PIN. If the call doesn’t route through to the conference, check your asterisk logs for details.

Trouble shooting tips:

  • If no curl result exists in the log, you may have typed or pasted something wrong in the curl url

  • SSH into your asterisk system and try a curl directly from terminal, confirm it works and check against dial plan

  • If terminal displays an error, lookup the error and try to isolate where the issue is. Also, may be worth trying curl from another system to compare

  • If terminal curl responds “curl: (52) Empty reply from server” , your using an outdated curl package. Check your curl version ( curl –-version ). Anything older than 7.49 doesn’t support http2 , but technically you should be using 7.70 as of May 2020.


Great job…

1 Like

Maybe new PHP versions are different. But Is your code not pretty volnurable to SQL injections? An attacker could take over the SQL Server and maybe further?

If I put in the “id” parameter
1’; SELECT more blabla; ’

I think you need to escape or clean the parameters first before usage.

@DSchaef Thanks for the feedback. I’m running inline intrusion detection, but one can never be too cautious.

If someone would like the modify the php and share , I’m sure others would like to see your suggestion implemented. Thanks :slight_smile:

Hi Craig,

first of all thanks for your involvement.
then I tried your method. It works but I have one issue. unfortunately a big one) :

I have no sound once the user connected.
where if I commit a call from the session to a phone I have sound in both directions.

May be it’s because of some changes I did but I’m not sure.

Here are the changes I did :

  • As I was already hosting some jitsi plugin files on it, I put the Jitsi_confPin.php on the freePBX server.
  • Instead of autoincrement the id in mysql I generate a 6 digit number in the php and insert it as id in mysql.

that’s all.

So as I say before :

  • I create a new session : OK
  • In the information panel I see the 6 digits number : OK
  • I call FreePBX extension and get access to a working IVR : OK
  • I enter the jitsi session with phone : OK
  • Can communicate in any direction : KO (But got sound in both direction if a call is commit from the conference)

Whereas If I go back to the legacy api : I have sound in both direction.

I can’t understand what’s happening. Any idea?

Sounds like a bad header. I had that problem numerous time as I was building my IVR.

The IVR should be taking the curl result and setting it as the room header. if the curl result contains the right room name, then call connects. if the header contains the right room name + some extra erroneous characters, like white spaces or carriage returns, you’ll still connect but the headers being exchanged will cause issues. Symptom being no audio in the rtp stream.

If you’re using FreePBX, set your dialplan to display your curl result in the logs using a “verbose” statement. Check the asterisk log for whitespeaces in the curl result. If that’s the case, reconfigure your curl command to remove the white spaces. (If you’re using my IVR Tutorial, the verbose statement is already there… you can just skip to viewing the logs)

Other approach to that could also be figuring out why the white spaces are sent, if that’s what’s happening

Ok, I’ll try that but I’m totally noob in Astrisk or FreePBX.

set your dialplan to display your curl result in the logs using a “verbose” statement.

This is already done in your custom extension script here, no ? :
exten => s,n,Verbose(0, ${CURL_RESULT});

Thanks by advance.

curl and header seem ok no hidden character:

[2020-05-14 15:41:50] VERBOSE[25704][C-00000007] pbx.c: Executing [s@Jitsi-Conference-Entry:16] Set("PJSIP/OVH-Trunk_Seconde_0033973765876-00000006", "CURL_RESULT=siptest")
[2020-05-14 15:41:53] VERBOSE[25704][C-00000007] pbx.c: Executing [s@Jitsi-Conference-Entry:20] SIPAddHeader("PJSIP/OVH-Trunk_Seconde_0033973765876-00000006", "Jitsi-Conference-Room:siptest")

Or may be I did not understand what I’m searching for. As I really am a noob.

You were right, there was an invisible \n

I generate de 6 digits like this :

$id = sprintf("%06d", mt_rand(1, 999999));
$sql = "INSERT INTO $db_table (id,conference) VALUES ('$id','$conference')";

May be the sprintf or the rand is the cause.
Anyway : Thank you it WOOOOORKS !!

1 Like

@DSchaef thanks again for the feedback. I had a little time to fiddle… re-posted the php using prepared statements and also restricted the user inputs. Should help those not running IDS :slight_smile:

This fixed our issue as well. For some reason the original code on our server was creating 2 database entries for every PHP call from jitsi. This resulted in it not displaying any dial-in information in the GUI because the request to get the dial-in code was returning 2 results so the source code reported invalid JSON.
Thank you!

Continuing the discussion from Tutorial - Self hosted Conference Mapper API:

Thanks for nice post. I have installed Kamailio as SIP Server. Can we integrate Kamailio to map the jitsi conference so that my SIP endpoints can join the meeting.

Please advice.


Hi @Craig_Eustice
Thanks a lot for this great post.
I’m having 2 issues. I setup both the two procedures


The 1st issue is that The PIN and ID are not showing on “Share” .
But when I call to my DID number, it send me to the conference and it is asking me for my Conf ID. Then I put a fake ID.

The 2nd issue that after I put the Conference ID it takes exactly 2mn silent before it asked me the PIN, I gave a Fake one then it said please contact support, goodbye.
Please can you help ?
Thanks in advance.

I fixed the issue about the silent call by copying again the dialplan from Tutorial - Jitsi / Jigasi & FreePBX integration. Along with Asterisk IVR to use Jitsi conference mapper API
I don;t have anymore silent.
The main issue now is that I’m not able to display the Phone, PIN and Password
Please can you help?

Do you have these settings in your config.js?

    dialInNumbersUrl: 'https://api.jitsi.net/phoneNumberList',
    dialInConfCodeUrl:  'https://api.jitsi.net/conferenceMapper',

Or maybe you have errors in the produced json output, check browser js console whether you see some errors?

Hi damencho
Thank you for your quick response
below my configuration:

I tried with what you suggested also, but still the same no information for Number, PIN and Password
When I run developer tools on Chrome, there is no error,
Kindly any other suggestion?

Maybe the problem is that it is http … just guessing. Check the network tab in the developer console when loading the page, do you see the requests for these addresses.

Hi @damencho

I installed let’sencrypt SSL on my FreePBX and added https but the issue is the same

When I click on “Invite More People” I get the log below:

Content.js:122 @atlaskit/modal-dialog: Deprecation warning - Use of the footer prop in ModalDialog is deprecated. Please compose your ModalDialog using the ‘components’ prop instead
value @ Content.js:122
Os @ react-dom.production.min.js:238
t.unstable_runWithPriority @ scheduler.production.min.js:20
ha @ react-dom.production.min.js:113
Ss @ react-dom.production.min.js:230
ms @ react-dom.production.min.js:206
(anonymous) @ react-dom.production.min.js:114
t.unstable_runWithPriority @ scheduler.production.min.js:20
ha @ react-dom.production.min.js:113
ba @ react-dom.production.min.js:114
ga @ react-dom.production.min.js:113
bs @ react-dom.production.min.js:207
zn @ react-dom.production.min.js:86

Hi @damencho

When I start a conference session, then I do a select in jitsiapi table, it’s giving me an empty table. Is it normal?
Thank you

mysql> select * from jitsiapi;
Empty set (0.00 sec)

mysql> desc jitsiapi;
| Field | Type | Null | Key | Default | Extra |
| id | int | NO | PRI | NULL | auto_increment |
| conference | varchar(255) | NO | UNI | NULL | |
2 rows in set (0.00 sec)


And when running a conference below the jicofo.log

Jicofo 2020-12-06 01:08:35.029 INFO: [63] org.jitsi.jicofo.xmpp.FocusComponent.log() Focus request for room: boukar20@conference.meet.mydomain.com
Jicofo 2020-12-06 01:08:35.030 INFO: [63] org.jitsi.jicofo.auth.AbstractAuthAuthority.log() Authenticated jid: az0l6cgupflrvte1@guest.meet.mydomain.com/a263rOjf with session: AuthSession[ID=gobetest@meet.mydomain.com, JID=az0l6cgupflrvte1@guest.meet.mydomain.com/a263rOjf, SID=bd61d4da-1310-4160-93d3-d1b3690c2b66, MUID=227aa31494aa4835e72cc8a36c36e03f, LIFE_TM_SEC=5886, R=hamadou@conference.meet.mydomain.com]@234918426
Jicofo 2020-12-06 01:08:35.030 INFO: [63] org.jitsi.jicofo.auth.AbstractAuthAuthority.log() Jid az0l6cgupflrvte1@guest.meet.mydomain.com/a263rOjf authenticated as: gobetest@meet.mydomain.com
Jicofo 2020-12-06 01:08:35.030 INFO: [63] org.jitsi.jicofo.FocusManager.log() Created new focus for boukar20@conference.meet.mydomain.com@auth.meet.mydomain.com. Conference count 1,options: call_control=callcontrol.meet.mydomain.com channelLastN=4 enableLipSync=false startAudioMuted=10 startVideoMuted=10 openSctp=true disableRtx=false
Jicofo 2020-12-06 01:08:35.031 INFO: [63] org.jitsi.jicofo.JitsiMeetConferenceImpl.log() Joining the room: boukar20@conference.meet.mydomain.com
Jicofo 2020-12-06 01:08:35.965 INFO: [32] org.jitsi.jicofo.ChatRoomRoleAndPresence.log() Chat room event ChatRoomMemberPresenceChangeEvent[type=MemberJoined sourceRoom=org.jitsi.impl.protocol.xmpp.ChatRoomImpl@59e09b5d member=ChatMember[boukar20@conference.meet.mydomain.com/fb4bd43b, jid: null]@708224463]
Jicofo 2020-12-06 01:08:35.970 INFO: [32] org.jitsi.jicofo.JitsiMeetConferenceImpl.log() Member boukar20@conference.meet.mydomain.com/fb4bd43b joined.