diff --git a/bigbluebutton-html5/private/static/guest-wait/guest-wait.html b/bigbluebutton-html5/private/static/guest-wait/guest-wait.html
index e2181188dbd3e66fe4e4901175bc28668dd5ff8b..91d89a410d9769164e37f7d2fa4cfb26476c69fd 100755
--- a/bigbluebutton-html5/private/static/guest-wait/guest-wait.html
+++ b/bigbluebutton-html5/private/static/guest-wait/guest-wait.html
@@ -61,11 +61,13 @@
   </style>
 
   <script type="text/javascript">
+    const REDIRECT_TIMEOUT = 15000;
+
     function updateMessage(message) {
       document.querySelector('#content > p').innerHTML = message;
     }
 
-    var lobbyMessage = '';
+    let lobbyMessage = '';
     function updateLobbyMessage(message) {
       if (message !== lobbyMessage) {
         lobbyMessage = message;
@@ -91,25 +93,42 @@
       const urlTest = new URL(`${window.location.origin}${GUEST_WAIT_ENDPOINT}`);
       const concatedParams = sessionToken.concat('&redirect=false');
       urlTest.search = concatedParams;
-      return fetch(urlTest, {method: 'get'});
+      return fetch(urlTest, { method: 'get' });
+    };
+
+    function redirect(message, url) {
+      disableAnimation();
+      updateMessage(message);
+      setTimeout(() => {
+        window.location = url;
+      }, REDIRECT_TIMEOUT);
     };
 
     function pollGuestStatus(token, attempt, limit, everyMs) {
-      setTimeout(function() {
-        var REDIRECT_STATUSES = ['ALLOW', 'DENY'];
 
+      setTimeout(function() {
         if (attempt >= limit) {
           disableAnimation();
-          updateMessage('No response from Moderator');
+          updateMessage('No response from a moderator.');
           return;
         }
 
         fetchGuestWait(token)
         .then(async (resp) => await resp.json())
         .then((data) => {
-          var status = data.response.guestStatus;
+          const code = data.response.returncode;
+
+          if (code === 'FAILED') {
+            return redirect(data.response.message, data.response.url);
+          }
+
+          const status = data.response.guestStatus;
+
+          if (status === 'DENY') {
+            return redirect(data.response.message, data.response.url);
+          }
 
-          if (REDIRECT_STATUSES.includes(status)) {
+          if (status === 'ALLOW') {
             disableAnimation();
             window.location = data.response.url;
             return;
@@ -133,14 +152,14 @@
     window.onload = function() {
       enableAnimation();
       try {
-        var ATTEMPT_EVERY_MS = 5000;
-        var ATTEMPT_LIMIT = 100;
+        const ATTEMPT_EVERY_MS = 5000;
+        const ATTEMPT_LIMIT = 100;
 
-        var sessionToken = findSessionToken();
+        const sessionToken = findSessionToken();
 
-        if(!sessionToken) {
+        if (!sessionToken) {
           disableAnimation()
-          updateMessage('No session Token received');
+          updateMessage('No session token received.');
           return;
         }
 
@@ -148,7 +167,7 @@
       } catch (e) {
         disableAnimation();
         console.error(e);
-        updateMessage('Error: more details in the console');
+        updateMessage('Error: more details in the console.');
       }
     };
   </script>
diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy
index 77049971a325d2109d268c2ad0ce4cb289056fae..3a010a4cf437163031b3c0416c857cf8404be54d 100755
--- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy
+++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy
@@ -1283,141 +1283,112 @@ class ApiController {
     }
 
     ApiErrors errors = new ApiErrors()
-    boolean reject = false;
-    String sessionToken = sanitizeSessionToken(params.sessionToken)
+    String msgKey = "defaultKey"
+    String msgValue = "defaultValue"
+    String destURL = paramsProcessorUtil.getDefaultLogoutUrl()
 
-    UserSession us = getUserSession(sessionToken);
-    Meeting meeting = null;
+    // Do we have a sessionToken? If none, complain.
+    String sessionToken = sanitizeSessionToken(params.sessionToken)
+    if (sessionToken == null) {
+      msgKey = "missingToken"
+      msgValue = "Guest missing session token."
+      respondWithJSONError(msgKey, msgValue, destURL)
+      return
+    }
 
+    UserSession us = getUserSession(sessionToken)
     if (us == null) {
-      log.debug("No user with session token.")
-      reject = true;
-    } else {
-      meeting = meetingService.getMeeting(us.meetingID);
-      if (meeting == null || meeting.isForciblyEnded()) {
-        log.debug("Meeting not found.")
-        reject = true
-      }
+      msgKey = "missingSession"
+      msgValue = "Guest missing session."
+      respondWithJSONError(msgKey, msgValue, destURL)
+      return
     }
 
-    // Determine the logout url so we can send the user there.
-    String logoutUrl = us != null ? us.logoutUrl : paramsProcessorUtil.getDefaultLogoutUrl()
-
-    if (reject) {
-      response.addHeader("Cache-Control", "no-cache")
-      withFormat {
-        json {
-          def builder = new JsonBuilder()
-          builder.response {
-            returncode RESP_CODE_FAILED
-            message "Could not process waiting guest."
-            logoutURL logoutUrl
-          }
-          render(contentType: "application/json", text: builder.toPrettyString())
-        }
-      }
-    } else {
-      //check if exists the param redirect
-      boolean redirectClient = true;
-
-      // Get the client url we stored in the join api call before
-      // being told to wait.
-      String clientURL = us.clientUrl;
-      String lobbyMsg = meeting.getGuestLobbyMessage()
-      log.info("clientURL = " + clientURL)
-      log.info("redirect = ." + redirectClient)
-      if (!StringUtils.isEmpty(params.redirect)) {
-        try {
-          redirectClient = Boolean.parseBoolean(params.redirect);
-          log.info("redirect 2 = ." + redirectClient)
-        } catch (Exception e) {
-          redirectClient = true;
-        }
-      }
-
-      // The client url is ovewriten. Let's allow it.
-      if (!StringUtils.isEmpty(params.clientURL)) {
-        clientURL = params.clientURL;
-      }
+    Meeting meeting = meetingService.getMeeting(us.meetingID)
+    if (meeting == null) {
+      msgKey = "missingMeeting"
+      msgValue = "Meeting does not exist."
+      respondWithJSONError(msgKey, msgValue, destURL)
+      return
+    }
 
-      String guestWaitStatus = us.guestStatus
+    // Is this user joining a meeting that has been ended. If so, complain.
+    if (meeting.isForciblyEnded()) {
+      msgKey = "meetingEnded"
+      msgValue = "Meeting ended."
+      respondWithJSONError(msgKey, msgValue, destURL)
+      return
+    }
 
-      log.debug("GuestWaitStatus = " + guestWaitStatus)
+    String status = us.guestStatus
+    destURL = us.clientUrl
+    String lobbyMsg = meeting.getGuestLobbyMessage()
 
-      String msgKey = "guestAllowed"
-      String msgValue = "Guest allowed to join meeting."
+    Boolean redirectClient = true
+    if (!StringUtils.isEmpty(params.redirect)) {
+      try {
+        redirectClient = Boolean.parseBoolean(params.redirect)
+      } catch (Exception e) {
+        redirectClient = true
+      }
+    }
 
-      String destUrl = clientURL
-      log.debug("destUrl = " + destUrl)
+    String guestURL = paramsProcessorUtil.getDefaultGuestWaitURL() + "?sessionToken=" + sessionToken
 
-      if (guestWaitStatus.equals(GuestPolicy.WAIT)) {
-        meetingService.guestIsWaiting(us.meetingID, us.internalUserId);
-        clientURL = paramsProcessorUtil.getDefaultGuestWaitURL();
-        destUrl = clientURL + "?sessionToken=" + sessionToken
-        log.debug("GuestPolicy.WAIT - destUrl = " + destUrl)
+    switch (status) {
+      case GuestPolicy.WAIT:
+        meetingService.guestIsWaiting(us.meetingID, us.internalUserId)
+        destURL = guestURL
         msgKey = "guestWait"
-        msgValue = "Guest waiting for approval to join meeting."
+        msgValue = "Please wait for a moderator to approve you joining the meeting."
+
         // We force the response to not do a redirect. Otherwise,
         // the client would just be redirecting into this endpoint.
         redirectClient = false
+        break
+      case GuestPolicy.DENY:
+        destURL = meeting.getLogoutUrl()
+        msgKey = "guestDeny"
+        msgValue = "Guest denied of joining the meeting."
+        redirectClient = false
+        break
+      case GuestPolicy.ALLOW:
+        // IF the user was allowed to join but there is no room available in
+        // the meeting we must hold his approval
+        if (hasReachedMaxParticipants(meeting, us)) {
+          meetingService.guestIsWaiting(us.meetingID, us.internalUserId)
+          destURL = guestURL
+          msgKey = "seatWait"
+          msgValue = "Guest waiting for a seat in the meeting."
+          redirectClient = false
+          status = GuestPolicy.WAIT
+        }
+        break
+      default:
+        break
+    }
 
-        Map<String, Object> logData = new HashMap<String, Object>();
-        logData.put("meetingid", us.meetingID);
-        logData.put("extMeetingid", us.externMeetingID);
-        logData.put("name", us.fullname);
-        logData.put("userid", us.internalUserId);
-        logData.put("sessionToken", sessionToken);
-        logData.put("logCode", "guest_wait");
-        logData.put("description", "Guest waiting for approval.");
-
-        Gson gson = new Gson();
-        String logStr = gson.toJson(logData);
-
-        log.info(" --analytics-- data=" + logStr);
-
-      } else if (guestWaitStatus.equals(GuestPolicy.DENY)) {
-        destUrl = meeting.getLogoutUrl()
-        msgKey = "guestDenied"
-        msgValue = "Guest denied to join meeting."
-
-        Map<String, Object> logData = new HashMap<String, Object>();
-        logData.put("meetingid", us.meetingID);
-        logData.put("extMeetingid", us.externMeetingID);
-        logData.put("name", us.fullname);
-        logData.put("userid", us.internalUserId);
-        logData.put("sessionToken", sessionToken);
-        logData.put("logCode", "guest_denied");
-        logData.put("description", "Guest denied.");
-
-        Gson gson = new Gson();
-        String logStr = gson.toJson(logData);
-
-        log.info(" --analytics-- data=" + logStr);
-      }
-
-      if (redirectClient) {
-        log.info("Redirecting to ${destUrl}");
-        redirect(url: destUrl);
-      } else {
-        log.info("Successfully joined. Sending XML response.");
-        response.addHeader("Cache-Control", "no-cache")
-        withFormat {
-          json {
-            def builder = new JsonBuilder()
-            builder.response {
-              returncode RESP_CODE_SUCCESS
-              messageKey msgKey
-              message msgValue
-              meeting_id us.meetingID
-              user_id us.internalUserId
-              auth_token us.authToken
-              session_token session[sessionToken]
-              guestStatus guestWaitStatus
-              lobbyMessage lobbyMsg
-              url destUrl
-            }
-            render(contentType: "application/json", text: builder.toPrettyString())
+    if (redirectClient) {
+      // User may join the meeting
+      redirect(url: destURL)
+    } else {
+      response.addHeader("Cache-Control", "no-cache")
+      withFormat {
+        json {
+          def builder = new JsonBuilder()
+          builder.response {
+            returncode RESP_CODE_SUCCESS
+            messageKey msgKey
+            message msgValue
+            meeting_id us.meetingID
+            user_id us.internalUserId
+            auth_token us.authToken
+            session_token session[sessionToken]
+            guestStatus status
+            lobbyMessage lobbyMsg
+            url destURL
           }
+          render(contentType: "application/json", text: builder.toPrettyString())
         }
       }
     }
@@ -2290,6 +2261,22 @@ class ApiController {
     return false;
   }
 
+  private void respondWithJSONError(msgKey, msgValue, destUrl) {
+    response.addHeader("Cache-Control", "no-cache")
+    withFormat {
+      json {
+        def builder = new JsonBuilder()
+        builder.response {
+          returncode RESP_CODE_FAILED
+          messageKey msgKey
+          message msgValue
+          url destUrl
+        }
+        render(contentType: "application/json", text: builder.toPrettyString())
+      }
+    }
+  }
+
   private void respondWithErrors(errorList, redirectResponse = false) {
     log.debug CONTROLLER_NAME + "#invalid"
     if (redirectResponse) {