diff --git a/composer.json b/composer.json
index 6c97470805a9f510515b34904fef1177d817ae36..2bd2726fe12f1c88bd794daddc8238b9db6b29aa 100644
--- a/composer.json
+++ b/composer.json
@@ -157,7 +157,7 @@
         "drupal/realname": "1.0.0-rc2",
         "drupal/rebuild_cache_access": "1.7",
         "drupal/recaptcha": "2.5",
-        "drupal/recaptcha_v3": "^1.3",
+        "drupal/recaptcha_v3": "^1.4",
         "drupal/redirect": "1.6",
         "drupal/redis": "1.0",
         "drupal/roleassign": "1.0.0-beta1",
diff --git a/composer.lock b/composer.lock
index 3cfc95e1442f4d01d0d312a43b43aba26138161d..ded05a579f64fee27cafd92b2d362620688ebf2a 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,7 +4,7 @@
         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
         "This file is @generated automatically"
     ],
-    "content-hash": "483958ad644d5bd3351af88c32212e2b",
+    "content-hash": "74fb2280bfa0733282c54f4704298ed2",
     "packages": [
         {
             "name": "alchemy/zippy",
@@ -6993,17 +6993,17 @@
         },
         {
             "name": "drupal/recaptcha_v3",
-            "version": "1.3.0",
+            "version": "1.4.0",
             "source": {
                 "type": "git",
                 "url": "https://git.drupalcode.org/project/recaptcha_v3.git",
-                "reference": "8.x-1.3"
+                "reference": "8.x-1.4"
             },
             "dist": {
                 "type": "zip",
-                "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.3.zip",
-                "reference": "8.x-1.3",
-                "shasum": "1a228dafb57317889c18bbc80eafa40696673125"
+                "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.4.zip",
+                "reference": "8.x-1.4",
+                "shasum": "2f4624076ebb94ad0f49a957d0b4b77094a2bf69"
             },
             "require": {
                 "drupal/captcha": "^1.0.0-beta4",
@@ -7013,8 +7013,8 @@
             "type": "drupal-module",
             "extra": {
                 "drupal": {
-                    "version": "8.x-1.3",
-                    "datestamp": "1590393627",
+                    "version": "8.x-1.4",
+                    "datestamp": "1610128897",
                     "security-coverage": {
                         "status": "covered",
                         "message": "Covered by Drupal's security advisory policy"
@@ -7023,7 +7023,7 @@
             },
             "notification-url": "https://packages.drupal.org/8/downloads",
             "license": [
-                "GPL-2.0+"
+                "GPL-2.0-or-later"
             ],
             "authors": [
                 {
diff --git a/vendor/composer/installed.json b/vendor/composer/installed.json
index e0aade52f2bc82bf9887eaf6622d5bef176e2262..a8bb79c42a7e0cbf26e6927b53a2dc0d6d64fc7b 100644
--- a/vendor/composer/installed.json
+++ b/vendor/composer/installed.json
@@ -7207,18 +7207,18 @@
     },
     {
         "name": "drupal/recaptcha_v3",
-        "version": "1.3.0",
-        "version_normalized": "1.3.0.0",
+        "version": "1.4.0",
+        "version_normalized": "1.4.0.0",
         "source": {
             "type": "git",
             "url": "https://git.drupalcode.org/project/recaptcha_v3.git",
-            "reference": "8.x-1.3"
+            "reference": "8.x-1.4"
         },
         "dist": {
             "type": "zip",
-            "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.3.zip",
-            "reference": "8.x-1.3",
-            "shasum": "1a228dafb57317889c18bbc80eafa40696673125"
+            "url": "https://ftp.drupal.org/files/projects/recaptcha_v3-8.x-1.4.zip",
+            "reference": "8.x-1.4",
+            "shasum": "2f4624076ebb94ad0f49a957d0b4b77094a2bf69"
         },
         "require": {
             "drupal/captcha": "^1.0.0-beta4",
@@ -7228,8 +7228,8 @@
         "type": "drupal-module",
         "extra": {
             "drupal": {
-                "version": "8.x-1.3",
-                "datestamp": "1590393627",
+                "version": "8.x-1.4",
+                "datestamp": "1610128897",
                 "security-coverage": {
                     "status": "covered",
                     "message": "Covered by Drupal's security advisory policy"
@@ -7239,7 +7239,7 @@
         "installation-source": "dist",
         "notification-url": "https://packages.drupal.org/8/downloads",
         "license": [
-            "GPL-2.0+"
+            "GPL-2.0-or-later"
         ],
         "authors": [
             {
diff --git a/web/modules/recaptcha_v3/composer.json b/web/modules/recaptcha_v3/composer.json
index ae49e614392b807b41c6ef49feab9ecf8d7c6e71..c6a2ed8790869286cf5a4adabd49fe34efec8c4f 100644
--- a/web/modules/recaptcha_v3/composer.json
+++ b/web/modules/recaptcha_v3/composer.json
@@ -2,7 +2,7 @@
   "name": "drupal/recaptcha_v3",
   "description": "The reCaptcha V3 module provides integration with Google reCaptcha V3 and CAPTCHA module.",
   "type": "drupal-module",
-  "license": "GPL-2.0+",
+  "license": "GPL-2.0-or-later",
   "minimum-stability": "dev",
   "require": {
     "google/recaptcha": "^1.2",
diff --git a/web/modules/recaptcha_v3/js/recaptcha_v3.js b/web/modules/recaptcha_v3/js/recaptcha_v3.js
index 4ce96882fffadab59508632e7179b5a66d0223df..4d49bdda4902e2cfef1fb65d2ec0f525fad948fe 100644
--- a/web/modules/recaptcha_v3/js/recaptcha_v3.js
+++ b/web/modules/recaptcha_v3/js/recaptcha_v3.js
@@ -6,6 +6,44 @@
 (function ($, Drupal) {
   'use strict';
 
+  function updateTokenElement(element) {
+    // Wait for grecaptcha to be loaded.
+    if (typeof grecaptcha === 'undefined') {
+      var timer = setInterval(function() {
+        if (typeof grecaptcha !== 'undefined' || !element) {
+          clearInterval(timer);
+
+          if (element) {
+            doUpdateTokenElement(element);
+          }
+        }
+      }, 500);
+    }
+    else {
+      doUpdateTokenElement(element);
+    }
+  }
+
+  function doUpdateTokenElement(element) {
+    grecaptcha.ready(function () {
+      if (!element) {
+        return;
+      }
+
+      var $element = $(element);
+
+      grecaptcha.execute(
+          $element.data('recaptchaV3SiteKey'),
+          {
+            action: $element.data('recaptchaV3Action')
+          }
+      ).then(function (token) {
+        $element.val(token);
+        $element.trigger('change');
+      });
+    });
+  }
+
   /**
    * Attach recaptcha response token from google with form.
    *
@@ -14,23 +52,25 @@
   Drupal.behaviors.reCaptchaV3 = {
     attach: function (context) {
       $('.recaptcha-v3-token', context).once('recaptcha-v3-token').each(function () {
-        var $token_element = $(this);
-        var timer = setInterval(function() {
-          if (grecaptcha !== undefined) {
-            grecaptcha.ready(function () {
-              grecaptcha.execute(
-                $token_element.data('recaptchaV3SiteKey'),
-                {
-                  action: $token_element.data('recaptchaV3Action')
-                }
-              ).then(function (token) {
-                $token_element.val(token);
-                $token_element.trigger('change');
-              });
-            });
-            clearInterval(timer)
+        var element = this;
+
+        updateTokenElement(element);
+
+        // Update the recaptcha tokens every 90 seconds.
+        // This seems to be the most robust way to always have valid recaptcha
+        // tokens when you don't have control over how the forms are being
+        // submitted. For example normal form submits are synchronous while
+        // Google Recaptcha v3 is asynchonous.
+        // A recaptcha token has a maximum lifetime of 120 seconds.
+        // https://developers.google.com/recaptcha/docs/v3
+        var interval = setInterval(function () {
+          if (!element) {
+            clearInterval(interval);
+          }
+          else {
+            updateTokenElement(element);
           }
-        }, 500);
+        }, 90000);
       });
     }
   };
diff --git a/web/modules/recaptcha_v3/recaptcha_v3.info.yml b/web/modules/recaptcha_v3/recaptcha_v3.info.yml
index 68f5329c751d2f1961c7d3ef4658b2e667f87ff9..f1792d08d892351643d96f46aaf4a511ae2798b1 100644
--- a/web/modules/recaptcha_v3/recaptcha_v3.info.yml
+++ b/web/modules/recaptcha_v3/recaptcha_v3.info.yml
@@ -8,7 +8,7 @@ configure: recaptcha_v3.settings
 dependencies:
   - captcha:captcha
 
-# Information added by Drupal.org packaging script on 2020-05-25
-version: '8.x-1.3'
+# Information added by Drupal.org packaging script on 2021-01-08
+version: '8.x-1.4'
 project: 'recaptcha_v3'
-datestamp: 1590393629
+datestamp: 1610128899
diff --git a/web/modules/recaptcha_v3/recaptcha_v3.module b/web/modules/recaptcha_v3/recaptcha_v3.module
index ffb42e8c83fff445082e0749866f70e0c22efdf0..982c5ba2b4b43dd23d03beb67cc299f3d4233d66 100644
--- a/web/modules/recaptcha_v3/recaptcha_v3.module
+++ b/web/modules/recaptcha_v3/recaptcha_v3.module
@@ -13,6 +13,7 @@
 use Drupal\Core\Routing\RouteMatchInterface;
 use Drupal\recaptcha_v3\ReCaptchaV3ActionInterface;
 use ReCaptcha\ReCaptcha;
+use ReCaptcha\RequestMethod\CurlPost;
 
 /**
  * Implements hook_help().
@@ -84,9 +85,9 @@ function recaptcha_v3_element_info_alter(array &$info) {
 function recaptcha_v3_pre_captcha_element_process(array &$element, FormStateInterface $form_state, array &$complete_form) {
   // If form is processed input then recaptcha v3 response should be in
   // form values and need replace reCAPTCHA v3 element by fallback
-  // challenge before captcha module lement process callback, because otherwise
-  // in case of error form will not rebuild and recaptcha v3 element will
-  // return again.
+  // challenge before captcha module element process callback, because,
+  // otherwise, in case of error, form will not rebuild
+  // and recaptcha v3 element will return again.
   if ($form_state->isProcessingInput()) {
 
     \Drupal::moduleHandler()->loadInclude('captcha', 'inc', 'captcha');
@@ -126,6 +127,7 @@ function recaptcha_v3_captcha($op, $captcha_type = '', $captcha_sid = NULL) {
         $captcha['form']['captcha_response'] = [
           '#type' => 'hidden',
           '#default_value' => '',
+          '#recaptcha_v3' => TRUE,
           '#attributes' => [
             // Need add id, because element should have id or
             // 'selector' property should exist in #ajax array
@@ -145,6 +147,11 @@ function recaptcha_v3_captcha($op, $captcha_type = '', $captcha_sid = NULL) {
             'event' => 'change',
           ],
         ];
+        // Flag that indicates that current captcha element is recaptcha_v3.
+        $captcha['form']['is_recaptcha_v3'] = [
+          '#type' => 'hidden',
+          '#value' => 1,
+        ];
         $captcha['solution'] = TRUE;
         $captcha['captcha_validate'] = 'recaptcha_v3_validate';
         $captcha['cacheable'] = (bool) $config->get('cacheable');
@@ -169,8 +176,18 @@ function recaptcha_v3_captcha($op, $captcha_type = '', $captcha_sid = NULL) {
  *   Set recaptcha v3 challenge if not already validated.
  */
 function recaptcha_v3_post_captcha_element_process(array &$element, FormStateInterface $form_state, array &$complete_form) {
-  if ($form_state->getTemporaryValue('recaptcha_v3_action_name') && !$form_state->has('recaptcha_v3')) {
-    $element['#captcha_validate'] = 'recaptcha_v3_validate';
+  // If value exist, then form was submitted.
+  if ($form_state->getTemporaryValue('recaptcha_v3_action_name')) {
+    $user_input = $form_state->getUserInput();
+    // If value is empty, then fallback widget already used and no need to
+    // run recaptcha v3 validation.
+    if (!empty($user_input['is_recaptcha_v3'])) {
+      $element['#captcha_validate'] = 'recaptcha_v3_validate';
+    }
+  }
+  // Hide description for the recaptcha v3 captcha element.
+  if (!empty($element['captcha_widgets']['captcha_response']['#recaptcha_v3'])) {
+    unset($element['#description']);
   }
   return $element;
 }
@@ -179,18 +196,14 @@ function recaptcha_v3_post_captcha_element_process(array &$element, FormStateInt
  * CAPTCHA Callback; Validates the reCAPTCHA v3 code.
  */
 function recaptcha_v3_validate($solution, $captcha_response, $element, FormStateInterface $form_state) {
-  // reCAPTCHA v3 was verified in one of the previous request.
-  if ($form_state->has('recaptcha_v3')) {
-    return (bool) $form_state->get(['recaptcha_v3', 'success']);
-  }
-  // Using user input instead of $captcha_response variable, because stupid
-  // recaptcha module wrongly use 'captcha_response' form element -
-  // instead of '#default_value' it using '#value' form api key:
+  // Using user input instead of $captcha_response variable, because
+  // recaptcha using '#value' form api key for the 'captcha_response' form
+  // element:
   // $captcha['form']['captcha_response'] = [
   //  '#type' => 'hidden',
   //  '#value' => 'Google no captcha', // Problem is here
   // ];
-  // so if using recaptcha as fallback challenge, $captcha_response is always
+  // So if using recaptcha as fallback challenge, $captcha_response is always
   // have 'Google no captcha' value.
   $user_input = $form_state->getUserInput();
   if (!empty($user_input['captcha_response'])) {
@@ -198,38 +211,56 @@ function recaptcha_v3_validate($solution, $captcha_response, $element, FormState
   }
 
   $captcha_type_challenge = $form_state->getTemporaryValue('recaptcha_v3_action_name');
-  // Verify submitted reCAPTCHA v3 token.
+  /** @var ReCaptchaV3ActionInterface $recaptcha_v3 */
   $recaptcha_v3 = ReCaptchaV3Action::load($captcha_type_challenge) ?? ReCaptchaV3Action::create([
     'id' => '',
     'label' => '',
     'threshold' => 1,
     'challenge' => 'default',
   ]);
+  // Verify submitted reCAPTCHA v3 token.
   $verification_response = _recaptcha_v3_verify_captcha_response($recaptcha_v3, $captcha_response);
 
   if (!$verification_response['success']) {
     // If we here, then token verification failed.
     if ($verification_response['error-codes']) {
       $errors = [];
+
+      $challenge = $recaptcha_v3->getChallenge();
+      if ($challenge === 'default') {
+        $challenge = \Drupal::config('recaptcha_v3.settings')
+          ->get('default_challenge');
+      }
+
       foreach ($verification_response['error-codes'] as $code) {
+        // If we have fallback challenge then do not log the threshold errors.
+        if ($challenge && $code === 'score-threshold-not-met') {
+          continue;
+        }
         $errors[] = recaptcha_v3_error_by_code($code);
       }
-      $errors_string = implode(' ', $errors);
-      \Drupal::logger('recaptcha_v3')->error('Google reCAPTCHA v3 validation failed: @error', ['@error' => $errors_string]);
+
+      if ($errors) {
+        $errors_string = implode(' ', $errors);
+        \Drupal::logger('recaptcha_v3')->error(
+          'Google reCAPTCHA v3 validation failed: @error',
+          ['@error' => $errors_string]
+        );
+      }
     }
-    $form_state->clearErrors();
+
     $error_message = \Drupal::config('recaptcha_v3.settings')
       ->get('error_message');
-    if ($error_message) {
-      $form_state->setErrorByName('captcha_response', $error_message);
-    }
   }
-  else {
+
+  // If captcha validated, then need to remove error related to the
+  // captcha_response element. Otherwise, for example, if fallback is
+  // captcha Math we will get error about exceeding input value length due to
+  // recaptcha v3 response is much longer than allowed for Math captcha.
+  // In another case if we have custom error message, then need to clear
+  // all current 'captcha_response' element error messages either.
+  if ($verification_response['success'] || !empty($error_message)) {
     $errors = $form_state->getErrors();
-    // If captcha validated, then need to remove error related to the
-    // captcha_response element. Otherwise, for example, if fallback is
-    // captcha Math we will get error about exceeding input value length due to
-    // recaptcha v3 response is much longer than allowed for Math captcha.
     if (isset($errors['captcha_response'])) {
       $form_state->clearErrors();
       foreach ($errors as $name => $error) {
@@ -239,15 +270,10 @@ function recaptcha_v3_validate($solution, $captcha_response, $element, FormState
       }
     }
   }
-  // Save captcha response in $form_state storage to prevent
-  // further validation requests.
-  $form_state->set('recaptcha_v3', $verification_response);
 
-  // Need to cache form, because previous line does not make sense
-  // because form will not cached otherwise. Probably this is wrong because
-  // if form set for rebuilding it will not cached anyway.
-  // @todo need to check if need to move next line to the element preprocess callback.
-  $form_state->setCached();
+  if(!empty($error_message)) {
+    $form_state->setErrorByName('captcha_response', $error_message);
+  }
 
   return (bool) $verification_response['success'];
 }
@@ -284,7 +310,7 @@ function recaptcha_v3_ajax_callback(array $form, FormStateInterface $form_state)
 function _recaptcha_v3_verify_captcha_response(ReCaptchaV3ActionInterface $recaptcha_v_3_action, $captcha_response) {
   $request = Drupal::request();
   $config = Drupal::config('recaptcha_v3.settings');
-  $recaptcha = new ReCaptcha($config->get('secret_key'));
+  $recaptcha = new ReCaptcha($config->get('secret_key'), function_exists('curl_version') ? new CurlPost() : null);
 
   if ($config->get('verify_hostname')) {
     $recaptcha->setExpectedHostname($request->getHost());
diff --git a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php
index 5bba600e2b3d901d917826a1e5316fe02870e89b..e8f648baf6e47c015c4cc4bc1a79edc2a52f3e5e 100644
--- a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php
+++ b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3ActionForm.php
@@ -57,6 +57,7 @@ public function form(array $form, FormStateInterface $form_state) {
     $form['id'] = [
       '#type' => 'machine_name',
       '#default_value' => $recaptcha_v3_action->id(),
+      '#required' => TRUE,
       '#machine_name' => [
         'exists' => [ReCaptchaV3Action::class, 'load'],
       ],
@@ -69,6 +70,7 @@ public function form(array $form, FormStateInterface $form_state) {
       '#min' => 0,
       '#max' => 1,
       '#step' => 0.1,
+      '#required' => TRUE,
       '#default_value' => $recaptcha_v3_action->getThreshold(),
     ];
 
diff --git a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php
index 450e4df4c9bb69fde0782f879762bd8c2e03a955..da459e1c001c4d409265c99749d6c06455cb24dd 100644
--- a/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php
+++ b/web/modules/recaptcha_v3/src/Form/ReCaptchaV3SettingsForm.php
@@ -165,6 +165,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       ->set('verify_hostname', $values['verify_hostname'])
       ->set('default_challenge', $values['default_challenge'])
       ->set('error_message', $values['error_message'])
+      ->set('cacheable', $values['cacheable'])
       ->save();
 
     parent::submitForm($form, $form_state);