diff --git a/README.md b/README.md
index d2d4bfe..8d84ee9 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ The protected form is altered by `hp_form_alter` ctools plugins. These plugins d
 
 To implement your own plugin, create a `plugins/hp_form_alter` folder in your module folder and add the plugin file there. Your plugin needs to have a `'form_alter_callback'` which is the actual form alter function called by `hp_form_alter()`. The `form_alter_callback` gets `$form`, `$form_state` and `$config` as arguments. `$config` is the plugin configuration. 
 
-The plugin can have a configuration form that is defined by the form callback specified by the `'configuration_form'` plugin key. If you want to add `#element_validate` callbacks to the plugin file, add `_hp_load_element_ctools_plugin` as the first `#element_validate` callback, to make sure your plugin file is loaded during validation. You also need to add a `'#plugin' => $protected['plugin'],` to your form element to let `_hp_load_element_ctools_plugin()` know which plugin file to load. 
+The plugin can have a configuration form that is defined by the form callback specified by the `'configuration_form'` plugin key. If you want to add `#element_validate` callbacks to the plugin file, add `_hp_load_element_ctools_plugin` as the first `#element_validate` callback, to make sure your plugin file is loaded during validation. You also need to add a `'#hp_plugin' => $config['plugin'],` to your form element to let `_hp_load_element_ctools_plugin()` know which plugin file to load. 
 
 Look at plugins/hp_form_alter/fail_validation in this module for an example.
 
diff --git a/hp.admin.inc b/hp.admin.inc
index 3bbcd04..cb86610 100644
--- a/hp.admin.inc
+++ b/hp.admin.inc
@@ -154,19 +154,6 @@ function hp_settings_form($form, &$form_state) {
 }
 
 /**
- * Helper form validate callback.
- *
- * Allows configuration form #element_validate callbacks to be placed in the
- * plugin file.
- */
-function _hp_load_element_ctools_plugin($element, $form_state) {
-  if (!empty($element['#plugin'])) {
-    ctools_include('plugins');
-    ctools_get_plugins('hp', 'hp_form_alter', $element['#plugin']);
-  }
-}
-
-/**
  * AJAX callback to load the configuration form for the plugin.
  */
 function hp_protected_forms_ajax($form, $form_state) {
diff --git a/hp.module b/hp.module
index f122cbe..c741079 100644
--- a/hp.module
+++ b/hp.module
@@ -256,6 +256,34 @@ function hp_form_alter(&$form, &$form_state, $form_id) {
 }
 
 /**
+ * Helper form validate callback.
+ *
+ * If executed first, it allows configuration form validate callbacks to be
+ * placed in the plugin file by loading the plugin file.
+ */
+function _hp_load_element_ctools_plugin($element, $form_state) {
+  if (!empty($element['#hp_plugin'])) {
+    ctools_include('plugins');
+    ctools_get_plugins('hp', 'hp_form_alter', $element['#hp_plugin']);
+  }
+}
+
+/**
+ * Element validate callback.
+ *
+ * This callback is placed in the .module file since it is shared between
+ * several plugins.
+ */
+function hp_minimal_confidence_validate($element, &$form_state) {
+  if (!is_numeric($element['#value'])) {
+    form_error($element, t('Minimal value should be a number.'));
+  }
+  elseif ($element['#value'] < 0 || $element['#value'] > 100) {
+    form_error($element, t('Minimal value should be at least 0 and at most 100.'));
+  }
+}
+
+/**
  * Returns the current user's Human Presence session ID.
  */
 function hp_session_id() {
diff --git a/plugins/hp_form_alter/captcha.inc b/plugins/hp_form_alter/captcha.inc
new file mode 100644
index 0000000..9ed8f46
--- /dev/null
+++ b/plugins/hp_form_alter/captcha.inc
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * CAPTCHA hp_form_alter plugin.
+ */
+
+$plugin = array(
+  'title' => t('CAPTCHA after HP fail'),
+  'form_alter_callback' => 'hp_captcha_form_alter',
+  'configuration_form' => 'hp_captcha_configuration_form',
+);
+
+/**
+ * Form alter callback.
+ */
+function hp_captcha_form_alter(&$form, &$form_state, $config) {
+  if (isset($form['captcha']) && empty($form_state['hp_captcha'])) {
+    unset($form['captcha']);
+  }
+
+  if (!isset($form['#validate'])) {
+    $form['#validate'] = array();
+  }
+  array_unshift($form['#validate'], 'hp_captcha_form_validation');
+  // We need this #validate callback to load this plugin file during
+  // validation. See README.md.
+  array_unshift($form['#validate'], '_hp_load_element_ctools_plugin');
+  $form['#hp_plugin'] = $config['plugin'];
+  $form['#hp_config'] = $config;
+}
+
+/**
+ * Displays CAPTCHA if Human Presence is not 100% confident.
+ */
+function hp_captcha_form_validation($form, &$form_state) {
+  if (!empty($form_state['hp_captcha'])) {
+    return;
+  }
+  // Fetch the Human Presence check response.
+  $response = hp_check_session();
+
+  // If the response is 100% sure the user is a human, do not prevent submission.
+  if (!empty($response) && $response->signal == 'HUMAN' && $response->confidence >= $form['#hp_config']['minimal_confidence']) {
+    return;
+  }
+
+  // Otherwise fail the detection check and prevent form submission.
+  // Can't use form_set_error() since that prevents form rebuilding.
+  drupal_set_message(t('Sorry, we could not process your submission at this time. Please solve the CAPTCHA below.'), 'error');
+  watchdog('hp', 'Suspicious form submission blocked: <pre>@response</pre>', array('@response' => print_r($response, TRUE)), WATCHDOG_NOTICE);
+  $form_state['hp_captcha'] = TRUE;
+  $form_state['rebuild'] = TRUE;
+  // Store the hp_captcha $form_state flag.
+  form_set_cache($form['#build_id'], $form, $form_state);
+}
+
+/**
+ * Configuration form.
+ */
+function hp_captcha_configuration_form($complete_form, $form_state, $config) {
+  $config_form = array();
+  $config_form['minimal_confidence'] = array(
+    '#type' => 'textfield',
+    '#title' => t('The minimal confidence below which we display a CAPTCHA.'),
+    '#description' => t('Should be a number between 0 and 100. 0 means "always show CAPTCHA after form submission".'),
+    '#default_value' => $config['minimal_confidence'] ?: 100,
+    '#required' => TRUE,
+    '#element_validate' => [
+      'hp_minimal_confidence_validate',
+    ],
+    '#hp_plugin' => $config['plugin'],
+  );
+  return $config_form;
+}
diff --git a/plugins/hp_form_alter/fail_validation.inc b/plugins/hp_form_alter/fail_validation.inc
index 8b610b7..3396717 100644
--- a/plugins/hp_form_alter/fail_validation.inc
+++ b/plugins/hp_form_alter/fail_validation.inc
@@ -18,6 +18,10 @@ function hp_fail_validation_form_alter(&$form, &$form_state, $config) {
     $form['#validate'] = array();
   }
   array_unshift($form['#validate'], 'hp_fail_form_validation');
+  // We need this #validate callback to load this plugin file during
+  // validation. See README.md.
+  array_unshift($form['#validate'], '_hp_load_element_ctools_plugin');
+  $form['#hp_plugin'] = $config['plugin'];
   $form['#hp_config'] = $config;
 }
 
@@ -50,24 +54,9 @@ function hp_fail_validation_configuration_form($complete_form, $form_state, $con
     '#default_value' => $config['minimal_confidence'] ?: 100,
     '#required' => TRUE,
     '#element_validate' => [
-      // We need this #element_validate callback to load this plugin file
-      // during validation. See README.md.
-      '_hp_load_element_ctools_plugin',
       'hp_minimal_confidence_validate'
     ],
     '#plugin' => $config['plugin'],
   );
   return $config_form;
 }
-
-/**
- * Element validate callback.
- */
-function hp_minimal_confidence_validate($element, &$form_state) {
-  if (!is_numeric($element['#value'])) {
-    form_error($element, t('Minimal value should be a number.'));
-  }
-  elseif ($element['#value'] < 0 || $element['#value'] > 100) {
-    form_error($element, t('Minimal value should be at least 0 and at most 100.'));
-  }
-}
