diff --git a/.gitignore b/.gitignore
index a441b838..d34ebfb4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,5 +10,6 @@ webform.features.yml
 modules/webform_examples/webform_examples.features.yml
 modules/webform_example_composite/webform_example_composite.features.yml
 modules/webform_example_element/webform_example_element.features.yml
+modules/webform_example_remote_post/webform_example_remote_post.features.yml
 modules/webform_templates/webform_templates.features.yml
 modules/webform_node/webform_node.features.yml
diff --git a/config/schema/webform.schema.yml b/config/schema/webform.schema.yml
index 2bc619f2..d81a6b28 100644
--- a/config/schema/webform.schema.yml
+++ b/config/schema/webform.schema.yml
@@ -898,15 +898,6 @@ webform.handler.remote_post:
     type:
       label: 'Type'
       type: string
-    insert_url:
-      label: 'Insert URL'
-      type: uri
-    update_url:
-      label: 'Update URL'
-      type: uri
-    delete_url:
-      label: 'Delete URL'
-      type: uri
     excluded_data:
       type: sequence
       label: 'Excluded data'
@@ -916,18 +907,42 @@ webform.handler.remote_post:
     custom_data:
       label: 'Custom data'
       type: string
-    insert_custom_data:
-      label: 'Insert custom data'
-      type: string
-    update_custom_data:
-      label: 'Update custom data'
-      type: string
-    delete_custom_data:
-      label: 'Delete custom data'
+    custom_options:
+      label: 'Custom options'
       type: string
     debug:
       type: boolean
       label: 'Enable debugging'
+    completed_url:
+      label: 'Completed URL'
+      type: uri
+    completed_custom_data:
+      label: 'Completed custom data'
+      type: string
+    updated_url:
+      label: 'Updated URL'
+      type: uri
+    updated_custom_data:
+      label: 'Updated custom data'
+      type: string
+    deleted_url:
+      label: 'Deleted URL'
+      type: uri
+    deleted_custom_data:
+      label: 'Deleted custom data'
+      type: string
+    draft_url:
+      label: 'Draft URL'
+      type: uri
+    draft_custom_data:
+      label: 'Draft custom data'
+      type: string
+    converted_url:
+      label: 'Converted URL'
+      type: uri
+    converted_custom_data:
+      label: 'Converted custom data'
+      type: string
 
 webform.exporter.*:
   type: mapping
diff --git a/docs/DEVELOPMENT-CHEATSHEET.md b/docs/DEVELOPMENT-CHEATSHEET.md
index c9389c21..ae402b37 100644
--- a/docs/DEVELOPMENT-CHEATSHEET.md
+++ b/docs/DEVELOPMENT-CHEATSHEET.md
@@ -56,6 +56,7 @@ echo 'true' > webform.features.yml
 echo 'true' > modules/webform_examples/webform_examples.features.yml
 echo 'true' > modules/webform_example_element/webform_example_element.features.yml
 echo 'true' > modules/webform_example_composite/webform_example_composite.features.yml
+echo 'true' > modules/webform_example_element/webform_example_remote_post.features.yml
 echo 'true' > modules/webform_templates/webform_templates.features.yml
 echo 'true' > modules/webform_node/webform_node.features.yml
 
@@ -66,10 +67,12 @@ drush en -y webform\
   webform_examples\
   webform_examples\
   webform_example_element\
+  webform_example_remote_post\
   webform_templates\
   webform_test\
   webform_test_element\
   webform_test_handler\
+  webform_test_handler_remote_post\
   webform_test_options\
   webform_test_views\
   webform_test_translation\
@@ -87,10 +90,12 @@ drush features-export -y webform_demo_event_registration
 drush features-export -y webform_examples
 drush features-export -y webform_example_element
 drush features-export -y webform_example_composite
+drush features-export -y webform_example_remote_post
 drush features-export -y webform_templates
 drush features-export -y webform_testdrush cr
 drush features-export -y webform_test_element
 drush features-export -y webform_test_handler
+drush features-export -y webform_test_handler_remote_post
 drush features-export -y webform_test_options
 drush features-export -y webform_test_views
 drush features-export -y webform_test_translation
@@ -109,10 +114,12 @@ drush webform-tidy -y --dependencies webform_demo_event_registration
 drush webform-tidy -y --dependencies webform_examples
 drush webform-tidy -y --dependencies webform_example_element
 drush webform-tidy -y --dependencies webform_example_composite
+drush webform-tidy -y --dependencies webform_example_remote_post
 drush webform-tidy -y --dependencies webform_templates
 drush webform-tidy -y --dependencies webform_test
 drush webform-tidy -y --dependencies webform_test_element
 drush webform-tidy -y --dependencies webform_test_handler
+drush webform-tidy -y --dependencies webform_test_handler_remote_post
 drush webform-tidy -y --dependencies webform_test_options
 drush webform-tidy -y --dependencies webform_test_views
 drush webform-tidy -y --dependencies webform_test_translation
@@ -127,10 +134,12 @@ drush features-import -y webform_demo_application_evaluation
 drush features-import -y webform_examples
 drush features-import -y webform_example_element
 drush features-import -y webform_example_composite
+drush features-import -y webform_example_remote_post
 drush features-import -y webform_templates
 drush features-import -y webform_test
 drush features-import -y webform_test_element
 drush features-import -y webform_test_handler
+drush features-import -y webform_test_handler_remote_post
 drush features-import -y webform_test_options
 drush features-import -y webform_test_views
 drush features-import -y webform_test_translation
diff --git a/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_remote_post.yml b/modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml
similarity index 77%
rename from tests/modules/webform_test_handler/config/install/webform.webform.test_handler_remote_post.yml
rename to modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml
index 643a874a..0674a81b 100644
--- a/tests/modules/webform_test_handler/config/install/webform.webform.test_handler_remote_post.yml
+++ b/modules/webform_example_remote_post/config/install/webform.webform.example_remote_post.yml
@@ -3,41 +3,40 @@ status: open
 dependencies:
   enforced:
     module:
-      - webform_test_handler
+      - webform_example_remote_post
 open: null
 close: null
 uid: null
 template: false
-id: test_handler_remote_post
-title: 'Test: Handler: Remote post'
-description: 'Test remote post handler.'
-category: 'Test: Handler'
+id: example_remote_post
+title: 'Example: Remote post'
+description: 'An example of a webform submission posted to a remote server.'
+category: Example
 elements: |
   first_name:
     '#title': 'First name'
     '#type': textfield
     '#required': true
-    '#default_value': John
   last_name:
     '#title': 'Last name'
     '#type': textfield
     '#required': true
-    '#default_value': Smith
   email:
     '#title': Email
     '#type': email
     '#required': true
-    '#default_value': from@example.com
   subject:
     '#title': Subject
     '#type': textfield
     '#required': true
-    '#default_value': '{subject}'
   message:
     '#title': Message
     '#type': textarea
     '#required': true
-    '#default_value': '{message}'
+  confirmation_number:
+    '#title': 'Confirmation number'
+    '#type': value
+    '#value': '[webform_handler:remote_post:completed:confirmation_number]'
 css: ''
 javascript: ''
 settings:
@@ -85,9 +84,10 @@ settings:
   draft_auto_save: false
   draft_saved_message: ''
   draft_loaded_message: ''
-  confirmation_type: page
+  confirmation_type: inline
   confirmation_title: ''
-  confirmation_message: ''
+  confirmation_message: |
+    <p>Your confirmation number is [webform-submission:values:confirmation_number].</p>
   confirmation_url: ''
   confirmation_attributes: {  }
   confirmation_back: true
@@ -147,9 +147,6 @@ handlers:
     status: true
     weight: 1
     settings:
-      insert_url: 'http://default/webform_test/remote_post/insert'
-      update_url: 'http://default/webform_test/remote_post/update'
-      delete_url: 'http://default/webform_test/remote_post/delete'
       type: x-www-form-urlencoded
       excluded_data:
         serial: serial
@@ -170,13 +167,21 @@ handlers:
         entity_id: entity_id
         sticky: sticky
         notes: notes
+        confirmation_number: confirmation_number
       custom_data: |
         custom_all: true
-        custom_title: '[webform:title]: Submission #[webform_submission:serial]'
-      insert_custom_data: |
-        custom_insert: true
-      update_custom_data: |
-        custom_update: true
-      delete_custom_data: |
-        custom_delete: true
+      custom_options: ''
       debug: true
+      completed_url: '/webform_example_remote_post/completed'
+      completed_custom_data: |
+        custom_completed: true
+      updated_url: '/webform_example_remote_post/updated'
+      updated_custom_data: |
+        custom_updated: true
+      deleted_url: '/webform_example_remote_post/deleted'
+      deleted_custom_data: |
+        custom_deleted: true
+      draft_url: ''
+      draft_custom_data: ''
+      converted_url: ''
+      converted_custom_data: ''
diff --git a/modules/webform_example_remote_post/src/Controller/WebformExampleRemotePostController.php b/modules/webform_example_remote_post/src/Controller/WebformExampleRemotePostController.php
new file mode 100644
index 00000000..5ffd3111
--- /dev/null
+++ b/modules/webform_example_remote_post/src/Controller/WebformExampleRemotePostController.php
@@ -0,0 +1,36 @@
+<?php
+
+namespace Drupal\webform_example_remote_post\Controller;
+
+use Drupal\Component\Utility\Random;
+use Drupal\Core\Controller\ControllerBase;
+use Symfony\Component\HttpFoundation\JsonResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides route responses for example remote post.
+ */
+class WebformExampleRemotePostController extends ControllerBase {
+
+  /**
+   * Returns a remote post response.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   * @param string $type
+   *   Type of remote post request (completed, updated, or deleted)
+   *
+   * @return \Symfony\Component\HttpFoundation\JsonResponse
+   *   A JSON response with request status and message.
+   */
+  public function index(Request $request, $type) {
+    $random = new Random();
+    $json = [
+      'status' => 'success',
+      'message' => (string) $this->t('Processed @type request.', ['@type' => $type]),
+      'confirmation_number' => $random->name(20, TRUE),
+    ];
+    return new JsonResponse($json, 200);
+  }
+
+}
diff --git a/modules/webform_example_remote_post/webform_example_remote_post.info.yml b/modules/webform_example_remote_post/webform_example_remote_post.info.yml
new file mode 100644
index 00000000..16b8716e
--- /dev/null
+++ b/modules/webform_example_remote_post/webform_example_remote_post.info.yml
@@ -0,0 +1,7 @@
+name: 'Webform Remote Post Example'
+type: module
+description: 'Provides an example of a webform submission posted to a remote server.'
+package: 'Webform example'
+core: 8.x
+dependencies:
+  - 'drupal:webform'
diff --git a/modules/webform_example_remote_post/webform_example_remote_post.module b/modules/webform_example_remote_post/webform_example_remote_post.module
new file mode 100644
index 00000000..7f468df2
--- /dev/null
+++ b/modules/webform_example_remote_post/webform_example_remote_post.module
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Provides an example of a webform submission posted to a remote server.
+ */
+
+use Drupal\Core\Url;
+
+/**
+ * Implements hook_webform_load().
+ */
+function webform_example_remote_post_webform_load(array $entities) {
+  if (isset($entities['example_remote_post'])) {
+    // Reset remote post URL to the current base URL and base path.
+    /** @var \Drupal\webform\WebformInterface $webform */
+    $webform = $entities['example_remote_post'];
+    /** @var \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler $handler */
+    $handler = $webform->getHandler('remote_post');
+
+    $configuration = $handler->getConfiguration();
+    $states = ['completed', 'updated', 'deleted'];
+    foreach ($states as $state) {
+      if ($configuration['settings'][$state . '_url'] === "/webform_example_remote_post/$state") {
+        $configuration['settings'][$state . '_url'] = Url::fromRoute(
+          'webform_example_remote_post.remote_post',
+          ['type' => $state],
+          ['absolute' => TRUE]
+        )->toString();
+      }
+    }
+    $handler->setConfiguration($configuration);
+  }
+}
diff --git a/modules/webform_example_remote_post/webform_example_remote_post.routing.yml b/modules/webform_example_remote_post/webform_example_remote_post.routing.yml
new file mode 100644
index 00000000..c46a7ab9
--- /dev/null
+++ b/modules/webform_example_remote_post/webform_example_remote_post.routing.yml
@@ -0,0 +1,7 @@
+webform_example_remote_post.remote_post:
+  path: '/webform_example_remote_post/{type}'
+  defaults:
+    _controller: '\Drupal\webform_example_remote_post\Controller\WebformExampleRemotePostController::index'
+  requirements:
+    # Example remote post handler does not require any access controls.
+    _access: 'TRUE'
diff --git a/src/Plugin/WebformHandler/RemotePostWebformHandler.php b/src/Plugin/WebformHandler/RemotePostWebformHandler.php
index 538b7f15..5b918a9b 100644
--- a/src/Plugin/WebformHandler/RemotePostWebformHandler.php
+++ b/src/Plugin/WebformHandler/RemotePostWebformHandler.php
@@ -8,6 +8,7 @@ use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\webform\Plugin\WebformHandlerBase;
+use Drupal\webform\WebformInterface;
 use Drupal\webform\WebformSubmissionInterface;
 use Drupal\webform\WebformTokenManagerInterface;
 use GuzzleHttp\ClientInterface;
@@ -84,13 +85,16 @@ class RemotePostWebformHandler extends WebformHandlerBase {
    */
   public function getSummary() {
     $configuration = $this->getConfiguration();
-
-    // If the saving of results is disabled clear update and delete URL.
-    if ($this->getWebform()->getSetting('results_disabled')) {
-      $configuration['settings']['update_url'] = '';
-      $configuration['settings']['delete_url'] = '';
+    if (!$this->isResultsEnabled()) {
+      $configuration['settings']['updated_url'] = '';
+      $configuration['settings']['deleted_url'] = '';
+    }
+    if (!$this->isDraftEnabled()) {
+      $configuration['settings']['draft_url'] = '';
+    }
+    if (!$this->isConvertEnabled()) {
+      $configuration['settings']['converted_url'] = '';
     }
-
     return [
       '#settings' => $configuration['settings'],
     ] + parent::getSummary();
@@ -104,15 +108,21 @@ class RemotePostWebformHandler extends WebformHandlerBase {
     $excluded_data = array_combine($field_names, $field_names);
     return [
       'type' => 'x-www-form-urlencoded',
-      'insert_url' => '',
-      'update_url' => '',
-      'delete_url' => '',
       'excluded_data' => $excluded_data,
       'custom_data' => '',
-      'insert_custom_data' => '',
-      'update_custom_data' => '',
-      'delete_custom_data' => '',
+      'custom_options' => '',
       'debug' => FALSE,
+      // States.
+      'completed_url' => '',
+      'completed_custom_data' => '',
+      'updated_url' => '',
+      'updated_custom_data' => '',
+      'deleted_url' => '',
+      'deleted_custom_data' => '',
+      'draft_url' => '',
+      'draft_custom_data' => '',
+      'converted_url' => '',
+      'converted_custom_data' => '',
     ];
   }
 
@@ -121,33 +131,91 @@ class RemotePostWebformHandler extends WebformHandlerBase {
    */
   public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
     $webform = $this->getWebform();
-    $results_disabled = $webform->getSetting('results_disabled');
-
-    $form['insert_url'] = [
-      '#type' => 'url',
-      '#title' => $this->t('Insert URL'),
-      '#description' => $this->t('The full URL to POST to when a new webform submission is saved. E.g. http://www.mycrm.com/form_insert_handler.php'),
-      '#required' => TRUE,
-      '#default_value' => $this->configuration['insert_url'],
-    ];
 
-    $form['update_url'] = [
-      '#type' => 'url',
-      '#title' => $this->t('Update URL'),
-      '#description' => $this->t('The full URL to POST to when an existing webform submission is updated. E.g. http://www.mycrm.com/form_insert_handler.php'),
-      '#default_value' => $this->configuration['update_url'],
-      '#access' => !$results_disabled,
+    // States
+    $states = [
+      WebformSubmissionInterface::STATE_COMPLETED => [
+        'state' => $this->t('completed'),
+        'label' => $this->t('Completed'),
+        'description' => $this->t('Post data when submission is <b>completed</b>.'),
+        'access' => TRUE,
+      ],
+      WebformSubmissionInterface::STATE_UPDATED => [
+        'state' => $this->t('updated'),
+        'label' => $this->t('Updated'),
+        'description' => $this->t('Post data when submission is <b>updated</b>.'),
+        'access' => $this->isResultsEnabled(),
+      ],
+      WebformSubmissionInterface::STATE_DELETED => [
+        'state' => $this->t('deleted'),
+        'label' => $this->t('Deleted'),
+        'description' => $this->t('Post data when submission is <b>deleted</b>.'),
+        'access' => $this->isResultsEnabled(),
+      ],
+      WebformSubmissionInterface::STATE_DRAFT => [
+        'state' => $this->t('draft'),
+        'label' => $this->t('Draft'),
+        'description' => $this->t('Post data when <b>draft</b> is saved.'),
+        'access' => $this->isDraftEnabled(),
+      ],
+      WebformSubmissionInterface::STATE_CONVERTED => [
+        'state' => $this->t('converted'),
+        'label' => $this->t('Converted'),
+        'description' => $this->t('Post data when anonymous submission is <b>converted</b> to authenticated.'),
+        'access' => $this->isConvertEnabled(),
+      ],
     ];
+    foreach ($states as $state => $state_item) {
+      $state_url = $state . '_url';
+      $state_custom_data = $state . '_custom_data';
+      $t_args = [
+        '@state' => $state_item['state'],
+        '@title' => $state_item['label'],
+        '@url' => 'http://www.mycrm.com/form_' . $state . '_handler.php'
+      ];
+      $form[$state] = [
+        '#type' => 'details',
+        '#open' => ($state === WebformSubmissionInterface::STATE_COMPLETED),
+        '#title' => $state_item['label'],
+        '#access' => $state_item['access'],
+      ];
+      $form[$state]['description'] = [
+        '#markup' => $state_item['description'],
+        '#prefix' => '<div>',
+        '#suffix' => '</div>',
+      ];
+      if ($state === WebformSubmissionInterface::STATE_COMPLETED) {
+        $form[$state]['token'] = [
+          '#markup' => $this->t('Response data can be passed to submission data using [webform_handler:{machine_name}:{state}:{key}] tokens. (ie [webform_handler:remote_post:completed:confirmation_number])'),
+          '#prefix' => '<div>',
+          '#suffix' => '</div>',
+        ];
+      }
+      $form[$state][$state_url] = [
+        '#type' => 'url',
+        '#title' => $this->t('@title URL', $t_args),
+        '#description' => $this->t('The full URL to POST to when an existing webform submission is @state. E.g. @url', $t_args),
+        '#required' => ($state === WebformSubmissionInterface::STATE_COMPLETED),
+        '#parents' => ['settings', $state_url],
+        '#default_value' => $this->configuration[$state_url],
+      ];
+      $form[$state][$state_custom_data] = [
+        '#type' => 'webform_codemirror',
+        '#mode' => 'yaml',
+        '#title' => $this->t('@title custom data', $t_args),
+        '#description' => $this->t('Enter custom data that will be included when a webform submission is @state.', $t_args),
+        '#parents' => ['settings', $state_custom_data],
+        '#states' => ['visible' => [':input[name="settings[' . $state_url . ']"]' => ['filled' => TRUE]]],
+        '#default_value' => $this->configuration[$state_custom_data],
+      ];
+    }
 
-    $form['delete_url'] = [
-      '#type' => 'url',
-      '#title' => $this->t('Delete URL'),
-      '#description' => $this->t('The full URL to POST to call when a webform submission is deleted. E.g. http://www.mycrm.com/form_delete_handler.php'),
-      '#default_value' => $this->configuration['delete_url'],
-      '#access' => !$results_disabled,
+    // Settings.
+    $form['general'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Settings'),
     ];
-
-    $form['type'] = [
+    $form['general']['type'] = [
       '#type' => 'select',
       '#title' => $this->t('Post type'),
       '#description' => $this->t('Use x-www-form-urlencoded if unsure, as it is the default format for HTML webforms. You also have the option to post data in <a href="http://www.json.org/" target="_blank">JSON</a> format.'),
@@ -156,30 +224,10 @@ class RemotePostWebformHandler extends WebformHandlerBase {
         'json' => $this->t('JSON'),
       ],
       '#required' => TRUE,
+      '#parents' => ['settings', 'type'],
       '#default_value' => $this->configuration['type'],
     ];
-
-    $form['submission_data'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Submission data'),
-    ];
-    $form['submission_data']['excluded_data'] = [
-      '#type' => 'webform_excluded_columns',
-      '#title' => $this->t('Posted data'),
-      '#title_display' => 'invisible',
-      '#webform_id' => $webform->id(),
-      '#required' => TRUE,
-      '#parents' => ['settings', 'excluded_data'],
-      '#default_value' => $this->configuration['excluded_data'],
-    ];
-
-    $form['custom_data'] = [
-      '#type' => 'details',
-      '#title' => $this->t('Custom data'),
-      '#description' => $this->t('Custom data will take precedence over submission data. You may use tokens.'),
-    ];
-
-    $form['custom_data']['custom_data'] = [
+    $form['general']['custom_data'] = [
       '#type' => 'webform_codemirror',
       '#mode' => 'yaml',
       '#title' => $this->t('Custom data'),
@@ -187,49 +235,40 @@ class RemotePostWebformHandler extends WebformHandlerBase {
       '#parents' => ['settings', 'custom_data'],
       '#default_value' => $this->configuration['custom_data'],
     ];
-    $form['custom_data']['insert_custom_data'] = [
+    $form['general']['custom_options'] = [
       '#type' => 'webform_codemirror',
       '#mode' => 'yaml',
-      '#title' => $this->t('Insert data'),
-      '#description' => $this->t("Enter custom data that will be included when a new webform submission is saved."),
-      '#parents' => ['settings', 'insert_custom_data'],
-      '#states' => [
-        'visible' => [
-          [':input[name="settings[update_url]"]' => ['filled' => TRUE]],
-          'or',
-          [':input[name="settings[delete_url]"]' => ['filled' => TRUE]],
-        ],
-      ],
-      '#default_value' => $this->configuration['insert_custom_data'],
+      '#title' => $this->t('Custom options'),
+      '#description' => $this->t('Enter custom <a href=":href">request options</a> that will be used by the Guzzle HTTP client. Request options can included custom headers.', [':href' => 'http://docs.guzzlephp.org/en/stable/request-options.html']),
+      '#parents' => ['settings', 'custom_options'],
+      '#default_value' => $this->configuration['custom_options'],
     ];
-    $form['custom_data']['update_custom_data'] = [
-      '#type' => 'webform_codemirror',
-      '#mode' => 'yaml',
-      '#title' => $this->t('Update data'),
-      '#description' => $this->t("Enter custom data that will be included when a webform submission is updated."),
-      '#parents' => ['settings', 'update_custom_data'],
-      '#states' => ['visible' => [':input[name="settings[update_url]"]' => ['filled' => TRUE]]],
-      '#default_value' => $this->configuration['update_custom_data'],
-    ];
-    $form['custom_data']['delete_custom_data'] = [
-      '#type' => 'webform_codemirror',
-      '#mode' => 'yaml',
-      '#title' => $this->t('Delete data'),
-      '#description' => $this->t("Enter custom data that will be included when a webform submission is deleted."),
-      '#parents' => ['settings', 'delete_custom_data'],
-      '#states' => ['visible' => [':input[name="settings[delete_url]"]' => ['filled' => TRUE]]],
-      '#default_value' => $this->configuration['delete_custom_data'],
-    ];
-    $form['custom_data']['token_tree_link'] = $this->tokenManager->buildTreeLink();
-
-    $form['debug'] = [
+    $form['general']['debug'] = [
       '#type' => 'checkbox',
       '#title' => $this->t('Enable debugging'),
       '#description' => $this->t('If checked, posted submissions will be displayed onscreen to all users.'),
       '#return_value' => TRUE,
+      '#parents' => ['settings', 'debug'],
       '#default_value' => $this->configuration['debug'],
     ];
 
+    // Submission data.
+    $form['submission_data'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Submission data'),
+    ];
+    $form['submission_data']['excluded_data'] = [
+      '#type' => 'webform_excluded_columns',
+      '#title' => $this->t('Posted data'),
+      '#title_display' => 'invisible',
+      '#webform_id' => $webform->id(),
+      '#required' => TRUE,
+      '#parents' => ['settings', 'excluded_data'],
+      '#default_value' => $this->configuration['excluded_data'],
+    ];
+
+    $form['token_tree_link'] = $this->tokenManager->buildTreeLink();
+
     return $form;
   }
 
@@ -250,46 +289,39 @@ class RemotePostWebformHandler extends WebformHandlerBase {
    * {@inheritdoc}
    */
   public function postSave(WebformSubmissionInterface $webform_submission, $update = TRUE) {
-    $operation = ($update) ? 'update' : 'insert';
-    $this->remotePost($operation, $webform_submission);
+    $state = $webform_submission->getWebform()->getSetting('results_disabled') ? WebformSubmissionInterface::STATE_COMPLETED : $webform_submission->getState();
+    $this->remotePost($state, $webform_submission);
   }
 
   /**
    * {@inheritdoc}
    */
   public function postDelete(WebformSubmissionInterface $webform_submission) {
-    $this->remotePost('delete', $webform_submission);
+    $this->remotePost(WebformSubmissionInterface::STATE_DELETED, $webform_submission);
   }
 
   /**
    * Execute a remote post.
    *
-   * @param string $operation
-   *   The type of webform submission operation to be posted. Can be 'insert',
-   *   'update', or 'delete'.
+   * @param string $state
+   *   The state of the webform submission.
+   *   Either STATE_NEW, STATE_DRAFT, STATE_COMPLETED, STATE_UPDATED, or
+   *   STATE_CONVERTED depending on the last save operation performed.
    * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
    *   The webform submission to be posted.
    */
-  protected function remotePost($operation, WebformSubmissionInterface $webform_submission) {
-    $request_url = $this->configuration[$operation . '_url'];
-    if (empty($request_url)) {
+  protected function remotePost($state, WebformSubmissionInterface $webform_submission) {
+    if (empty($this->configuration[$state . '_url'])) {
       return;
     }
 
+    $request_url = $this->configuration[$state . '_url'];
     $request_type = $this->configuration['type'];
-    $request_post_data = $this->getPostData($operation, $webform_submission);
+    $request_options = (!empty($this->configuration['custom_options'])) ? Yaml::decode($this->configuration['custom_options']) : [];
+    $request_options[($request_type == 'json' ? 'json' :'form_params')] = $this->getRequestData($state, $webform_submission);
 
     try {
-      switch ($request_type) {
-        case 'json':
-          $response = $this->httpClient->post($request_url, ['json' => $request_post_data]);
-          break;
-
-        case 'x-www-form-urlencoded':
-        default:
-          $response = $this->httpClient->post($request_url, ['form_params' => $request_post_data]);
-          break;
-      }
+      $response = $this->httpClient->post($request_url, $request_options);
     }
     catch (RequestException $request_exception) {
       $message = $request_exception->getMessage();
@@ -299,38 +331,55 @@ class RemotePostWebformHandler extends WebformHandlerBase {
       $message = nl2br(htmlentities($message));
 
       // If debugging is enabled, display the error message on screen.
-      $this->debug($message, $operation, $request_url, $request_type, $request_post_data, $response, 'error');
+      $this->debug($message, $state, $request_url, $request_type, $request_options, $response, 'error');
 
       // Log error message.
       $context = [
         '@form' => $this->getWebform()->label(),
-        '@operation' => $operation,
+        '@state' => $state,
         '@type' => $request_type,
         '@url' => $request_url,
         '@message' => $message,
         'link' => $this->getWebform()->toLink($this->t('Edit'), 'handlers-form')->toString(),
       ];
-      $this->logger->error('@form webform remote @type post (@operation) to @url failed. @message', $context);
+      $this->logger->error('@form webform remote @type post (@state) to @url failed. @message', $context);
       return;
     }
 
     // If debugging is enabled, display the request and response.
-    $this->debug(t('Remote post successful!'), $operation, $request_url, $request_type, $request_post_data, $response, 'warning');
+    $this->debug(t('Remote post successful!'), $state, $request_url, $request_type, $request_options, $response, 'warning');
+
+    // Replace [webform_handler] tokens in submission data.
+    // Data structured for [webform_handler:remote_post:completed:key] tokens.
+    $submission_data = $webform_submission->getData();
+    $has_token = (strpos(print_r($submission_data, TRUE), '[webform_handler:' . $this->getHandlerId() . ':') !== FALSE) ? TRUE : FALSE;
+    if ($has_token) {
+      $response_data = $this->getResponseData($response);
+      $token_data = ['webform_handler' => [$this->getHandlerId() => [$state => $response_data]]];
+      $submission_data = $this->tokenManager->replace($submission_data, $webform_submission, $token_data);
+      $webform_submission->setData($submission_data);
+      // Save changes to the submission data without invoking any hooks
+      // or handlers.
+      if ($this->isResultsEnabled()) {
+        $this->submissionStorage->saveData($webform_submission);
+      }
+    }
   }
 
   /**
-   * Get a webform submission's post data.
+   * Get a webform submission's request data.
    *
-   * @param string $operation
-   *   The type of webform submission operation to be posted. Can be 'insert',
-   *   'update', or 'delete'.
+   * @param string $state
+   *   The state of the webform submission.
+   *   Either STATE_NEW, STATE_DRAFT, STATE_COMPLETED, STATE_UPDATED, or
+   *   STATE_CONVERTED depending on the last save operation performed.
    * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
    *   The webform submission to be posted.
    *
    * @return array
    *   A webform submission converted to an associative array.
    */
-  protected function getPostData($operation, WebformSubmissionInterface $webform_submission) {
+  protected function getRequestData($state, WebformSubmissionInterface $webform_submission) {
     // Get submission and elements data.
     $data = $webform_submission->toArray(TRUE);
 
@@ -347,9 +396,9 @@ class RemotePostWebformHandler extends WebformHandlerBase {
       $data = Yaml::decode($this->configuration['custom_data']) + $data;
     }
 
-    // Append operation data.
-    if (!empty($this->configuration[$operation . '_custom_data'])) {
-      $data = Yaml::decode($this->configuration[$operation . '_custom_data']) + $data;
+    // Append state custom data.
+    if (!empty($this->configuration[$state . '_custom_data'])) {
+      $data = Yaml::decode($this->configuration[$state . '_custom_data']) + $data;
     }
 
     // Replace tokens.
@@ -359,44 +408,76 @@ class RemotePostWebformHandler extends WebformHandlerBase {
   }
 
   /**
+   * Authentication remote post options and authentication tokens
+   *
+   * @param array $options
+   *   Request options including the form_params or json and the request header.
+   *
+   * @return array
+   *   The request options with authentication tokens add to the
+   *   parameters or header.
+   *
+   */
+  protected function authenticate(array $options) {
+    // Here you can set a custom authentication token to the remote post.
+    return $options;
+  }
+
+  /**
+   * Get response data
+   *
+   * @param \Psr\Http\Message\ResponseInterface $response
+   *   The response returned by the remote server.
+   *
+   * @return array|string
+   *   An array of data, parse from JSON, or a string.
+   */
+  protected function getResponseData(ResponseInterface $response) {
+    $body = (string) $response->getBody();
+    $data = json_decode($body, TRUE);
+    return (json_last_error() === JSON_ERROR_NONE) ? $data : $body;
+  }
+
+  /**
    * Display debugging information.
    *
    * @param string $message
    *   Message to be displayed.
-   * @param string $operation
-   *   The operation being performed, can be either insert, update, or delete.
+   * @param string $state
+   *   The state of the webform submission.
+   *   Either STATE_NEW, STATE_DRAFT, STATE_COMPLETED, STATE_UPDATED, or
+   *   STATE_CONVERTED depending on the last save operation performed.
    * @param string $request_url
    *   The remote URL the request is being posted to.
    * @param string $request_type
    *   The type of remote post.
-   * @param string $request_post_data
-   *   The webform submission data being posted.
+   * @param string $request_options
+   *   The requests options including the submission data..
    * @param \Psr\Http\Message\ResponseInterface|null $response
    *   The response returned by the remote server.
    * @param string $type
    *   The type of message to be displayed to the end use.
    */
-  protected function debug($message, $operation, $request_url, $request_type, $request_post_data, ResponseInterface $response = NULL, $type = 'warning') {
+  protected function debug($message, $state, $request_url, $request_type, $request_options, ResponseInterface $response = NULL, $type = 'warning') {
     if (empty($this->configuration['debug'])) {
       return;
     }
 
     $build = [
       '#type' => 'details',
-      '#title' => $this->t('Debug: Remote post: @title', ['@title' => $this->label()]),
+      '#title' => $this->t('Debug: Remote post: @title [@state]', ['@title' => $this->label(), '@state' => $state]),
     ];
 
-    // Operation.
-    $build['operation'] = [
+    // State.
+    $build['state'] = [
       '#type' => 'item',
-      '#title' => $this->t('Remote operation'),
-      '#markup' => $operation,
+      '#title' => $this->t('Submission state/operation:'),
+      '#markup' => $state,
       '#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'],
     ];
 
-    $build['returned'] = ['#markup' => '<hr />'];
-
     // Request.
+    $build['request'] = ['#markup' => '<hr />'];
     $build['request_url'] = [
       '#type' => 'item',
       '#title' => $this->t('Request URL'),
@@ -405,34 +486,33 @@ class RemotePostWebformHandler extends WebformHandlerBase {
     ];
     $build['request_type'] = [
       '#type' => 'item',
-      '#title' => $this->t('Request type'),
+      '#title' => $this->t('Request type:'),
       '#markup' => $request_type,
       '#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'],
     ];
-    $build['request_post_data'] = [
+    $build['request_options'] = [
       '#type' => 'item',
-      '#title' => $this->t('Request data'),
+      '#title' => $this->t('Request options:'),
       '#wrapper_attributes' => ['style' => 'margin: 0'],
       'data' => [
-        '#markup' => htmlspecialchars(Yaml::encode($request_post_data)),
+        '#markup' => htmlspecialchars(Yaml::encode($request_options)),
         '#prefix' => '<pre>',
         '#suffix' => '</pre>',
       ],
     ];
 
-    $build['returned'] = ['#markup' => '<hr />'];
-
     // Response.
+    $build['response'] = ['#markup' => '<hr />'];
     if ($response) {
       $build['response_code'] = [
         '#type' => 'item',
-        '#title' => $this->t('Response status code'),
+        '#title' => $this->t('Response status code:'),
         '#markup' => $response->getStatusCode(),
         '#wrapper_attributes' => ['class' => ['container-inline'], 'style' => 'margin: 0'],
       ];
       $build['response_header'] = [
         '#type' => 'item',
-        '#title' => $this->t('Response header'),
+        '#title' => $this->t('Response header:'),
         '#wrapper_attributes' => ['style' => 'margin: 0'],
         'data' => [
           '#markup' => htmlspecialchars(Yaml::encode($response->getHeaders())),
@@ -443,7 +523,7 @@ class RemotePostWebformHandler extends WebformHandlerBase {
       $build['response_body'] = [
         '#type' => 'item',
         '#wrapper_attributes' => ['style' => 'margin: 0'],
-        '#title' => $this->t('Response body'),
+        '#title' => $this->t('Response body:'),
         'data' => [
           '#markup' => htmlspecialchars($response->getBody()),
           '#prefix' => '<pre>',
@@ -459,7 +539,46 @@ class RemotePostWebformHandler extends WebformHandlerBase {
       ];
     }
 
+    // Message.
+    $build['message'] = ['#markup' => '<hr />'];
+    $build['message_message'] = [
+      '#type' => 'item',
+      '#wrapper_attributes' => ['style' => 'margin: 0'],
+      '#title' => $this->t('Message:'),
+      '#markup' => $message,
+    ];
+
     drupal_set_message(\Drupal::service('renderer')->renderPlain($build), $type);
   }
 
+  /**
+   * Determine if saving of results is enabled.
+   *
+   * @return bool
+   *   TRUE if saving of results is enabled.
+   */
+  protected function isResultsEnabled() {
+    return ($this->getWebform()->getSetting('results_disabled') === FALSE);
+  }
+
+  /**
+   * Determine if saving of draft is enabled.
+   *
+   * @return bool
+   *   TRUE if saving of draft is enabled.
+   */
+  protected function isDraftEnabled() {
+    return $this->isResultsEnabled() && ($this->getWebform()->getSetting('draft') != WebformInterface::DRAFT_NONE);
+  }
+
+  /**
+   * Determine if converting anoynmous submissions to authenticated is enabled.
+   *
+   * @return bool
+   *   TRUE if converting anoynmous submissions to authenticated is enabled.
+   */
+  protected function isConvertEnabled() {
+    return $this->isDraftEnabled() && ($this->getWebform()->getSetting('form_convert_anonymous') === TRUE);
+  }
+
 }
diff --git a/src/Tests/Handler/WebformHandlerRemotePostTest.php b/src/Tests/Handler/WebformHandlerRemotePostTest.php
index fae10476..4cb07b56 100644
--- a/src/Tests/Handler/WebformHandlerRemotePostTest.php
+++ b/src/Tests/Handler/WebformHandlerRemotePostTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\webform\Tests\Handler;
 
 use Drupal\webform\Entity\Webform;
+use Drupal\webform\Entity\WebformSubmission;
 use Drupal\webform\Tests\WebformTestBase;
 
 /**
@@ -17,7 +18,7 @@ class WebformHandlerRemotePostTest extends WebformTestBase {
    *
    * @var array
    */
-  public static $modules = ['webform', 'webform_test_handler'];
+  public static $modules = ['webform', 'webform_test_handler_remote_post'];
 
   /**
    * Webforms to load.
@@ -30,73 +31,109 @@ class WebformHandlerRemotePostTest extends WebformTestBase {
    * Test remote post handler.
    */
   public function testRemotePostHandler() {
-    /** @var \Drupal\webform\WebformInterface $webform_handler_remote */
-    $webform_handler_remote = Webform::load('test_handler_remote_post');
-
     $this->drupalLogin($this->rootUser);
 
-    // Check remote post 'create' operation.
-    $sid = $this->postSubmission($webform_handler_remote);
-    $this->assertPattern('#<label>Remote operation</label>\s+insert#ms');
-    $this->assertRaw('custom_insert: true');
-    $this->assertRaw('custom_all: true');
-    $this->assertRaw("custom_title: 'Test: Handler: Remote post: Submission #$sid'");
-    $this->assertRaw('first_name: John');
-    $this->assertRaw('last_name: Smith');
-    $this->assertRaw('email: from@example.com');
-    $this->assertRaw("subject: '{subject}'");
-    $this->assertRaw("message: '{message}'");
-    $this->assertNoRaw("sid: '$sid'");
+    /** @var \Drupal\webform\WebformInterface $webform */
+    $webform = Webform::load('test_handler_remote_post');
+
+    // Check 'completed' operation.
+    $sid = $this->postSubmission($webform);
+    $webform_submission = WebformSubmission::load($sid);
+    $this->assertRaw("form_params:
+  custom_completed: true
+  custom_data: true
+  response_type: '200'
+  first_name: John
+  last_name: Smith");
+    $this->assertRaw('Processed completed request.');
+
+    // Check confirmation number is set via the
+    // [webform_handler:remote_post:completed:confirmation_number] token.
+    $this->assertRaw('Your confirmation number is ' . $webform_submission->getData('confirmation_number') . '.');
+
+    // Check custom header.
+    $this->assertRaw('{&quot;custom_header&quot;:&quot;true&quot;}');
 
-    // Check remote post 'update' operation.
+    // Sleep for 1 second to make sure submission timestamp is updated.
+    sleep(1);
+
+    // Check 'updated' operation.
     $this->drupalPostForm("admin/structure/webform/manage/test_handler_remote_post/submission/$sid/edit", [], t('Save'));
-    $this->assertRaw('custom_update: true');
-    $this->assertRaw('custom_all: true');
-    $this->assertRaw("custom_title: 'Test: Handler: Remote post: Submission #$sid'");
-    $this->assertRaw('first_name: John');
-    $this->assertPattern('#<label>Remote operation</label>\s+update#ms');
+    $this->assertRaw("form_params:
+  custom_updated: true
+  custom_data: true
+  response_type: '200'
+  first_name: John
+  last_name: Smith");
+    $this->assertRaw('Processed updated request.');
 
-    // Check remote post 'delete' operation.
+    // Check 'deleted`' operation.
     $this->drupalPostForm("admin/structure/webform/manage/test_handler_remote_post/submission/$sid/delete", [], t('Delete'));
-    $this->assertRaw('custom_delete: true');
-    $this->assertRaw('custom_all: true');
-    $this->assertRaw("custom_title: 'Test: Handler: Remote post: Submission #$sid'");
-    $this->assertRaw('first_name: John');
-    $this->assertPattern('#<label>Remote operation</label>\s+delete#ms');
+    $this->assertRaw("form_params:
+  custom_deleted: true
+  custom_data: true
+  first_name: John
+  last_name: Smith
+  response_type: '200'");
+    $this->assertRaw('Processed deleted request.');
+
+    // Switch anonymous user.
+    $this->drupalLogout();
 
-    // Check including data.
-    $handler = $webform_handler_remote->getHandler('remote_post');
+    // Check 'draft' operation.
+    $this->postSubmission($webform, [], t('Save Draft'));
+    $this->assertRaw("form_params:
+  custom_draft: true
+  custom_data: true
+  response_type: '200'
+  first_name: John
+  last_name: Smith");
+    $this->assertRaw('Processed draft request.');
+
+    // Login root user.
+    $this->drupalLogin($this->rootUser);
+
+    // Check 'convert' operation.
+    $this->assertRaw("form_params:
+  custom_converted: true
+  custom_data: true
+  first_name: John
+  last_name: Smith
+  response_type: '200'");
+    $this->assertRaw('Processed converted request.');
+
+    // Check excluded data.
+    $handler = $webform->getHandler('remote_post');
     $configuration = $handler->getConfiguration();
     $configuration['settings']['excluded_data'] = [
-      'subject' => 'subject',
-      'message' => 'message',
+      'last_name' => 'last_name',
     ];
     $handler->setConfiguration($configuration);
-    $webform_handler_remote->save();
-    $sid = $this->postSubmission($webform_handler_remote);
+    $webform->save();
+    $sid = $this->postSubmission($webform);
     $this->assertRaw('first_name: John');
-    $this->assertRaw('last_name: Smith');
-    $this->assertRaw('email: from@example.com');
-    $this->assertNoRaw("subject: '{subject}'");
-    $this->assertNoRaw("message: '{message}'");
+    $this->assertNoRaw('last_name: Smith');
     $this->assertRaw("sid: '$sid'");
 
-    // @todo Figure out why the below test is failing on Drupal.org.
-    // Check remote post 'create' 500 error handling.
-    // $this->postSubmission($webform_handler_remote, ['first_name' => 'FAIL']);
-    // $this->assertPattern('#<label>Response status code</label>\s+500#ms');
-
-    // @todo Figure out why the below test is failing on Drupal.org.
-    // Update the remote post handlers insert url to return a 404 error.
-    // /** @var \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler $handler */
-    // $handler = $webform_handler_remote->getHandler('remote_post');
-    // $configuration = $handler->getConfiguration();
-    // $configuration['settings']['insert_url'] .= '/broken';
-    // $handler->setConfiguration($configuration);
-    // $webform_handler_remote->save();
-
-    // $this->postSubmission($webform_handler_remote, ['first_name' => 'FAIL']);
-    // $this->assertPattern('#<label>Response status code</label>\s+404#ms');
+    // Check 500 Internal Server Error.
+    $this->postSubmission($webform, ['response_type' => '500']);
+    $this->assertRaw('Failed to process completed request.');
+
+    // Check 404 Not Found.
+    $this->postSubmission($webform, ['response_type' => '404']);
+    $this->assertRaw('File not found');
+
+    // Disable saving of results
+    $webform->setSetting('results_disabled', TRUE);
+    $webform->save();
+
+    // Check confiramtion number when results disabled.
+    $sid = $this->postSubmission($webform);
+    $this->assertNull($sid);
+
+    // Get confiramtion number from JSON packet.
+    preg_match('/&quot;confirmation_number&quot;:&quot;([a-zA-z0-9]+)&quot;/', $this->getRawContent(), $match);
+    $this->assertRaw('Your confirmation number is ' . $match[1] . '.');
   }
 
 }
diff --git a/src/Tests/WebformTestTrait.php b/src/Tests/WebformTestTrait.php
index cae2aec8..a836922c 100644
--- a/src/Tests/WebformTestTrait.php
+++ b/src/Tests/WebformTestTrait.php
@@ -141,10 +141,17 @@ trait WebformTestTrait {
   /**
    * Get the last submission id.
    *
-   * @return int
-   *   The last submission id.
+   * @param \Drupal\webform\WebformInterface $webform
+   *   A webform.
+   *
+   * @return int|null
+   *   The last submission id. NULL is saving of results is disabled.
    */
-  protected function getLastSubmissionId($webform) {
+  protected function getLastSubmissionId(WebformInterface $webform) {
+    if ($webform->getSetting('results_disabled')) {
+      return NULL;
+    }
+
     // Get submission sid.
     $url = UrlHelper::parse($this->getUrl());
     if (isset($url['query']['sid'])) {
diff --git a/src/WebformSubmissionInterface.php b/src/WebformSubmissionInterface.php
index 01020d4c..5d5926e5 100644
--- a/src/WebformSubmissionInterface.php
+++ b/src/WebformSubmissionInterface.php
@@ -227,7 +227,7 @@ interface WebformSubmissionInterface extends ContentEntityInterface, EntityOwner
   /**
    * Track the state of a submission.
    *
-   * @return int
+   * @return string
    *   Either STATE_NEW, STATE_DRAFT, STATE_COMPLETED, STATE_UPDATED, or
    *   STATE_CONVERTED depending on the last save operation performed.
    */
diff --git a/src/WebformSubmissionStorage.php b/src/WebformSubmissionStorage.php
index bcc040fc..f29e9619 100644
--- a/src/WebformSubmissionStorage.php
+++ b/src/WebformSubmissionStorage.php
@@ -131,7 +131,6 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
     parent::buildPropertyQuery($entity_query, $values);
   }
 
-
   /**
    * {@inheritdoc}
    */
@@ -814,16 +813,14 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
    * {@inheritdoc}
    */
   public function invokeWebformHandlers($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) {
-    $webform = $webform_submission->getWebform();
-    $webform->invokeHandlers($method, $webform_submission, $context1, $context2);
+    $webform_submission->invokeWebformHandlers($method, $webform_submission, $context1, $context2);
   }
 
   /**
    * {@inheritdoc}
    */
   public function invokeWebformElements($method, WebformSubmissionInterface $webform_submission, &$context1 = NULL, &$context2 = NULL) {
-    $webform = $webform_submission->getWebform();
-    $webform->invokeElements($method, $webform_submission, $context1, $context2);
+    $webform_submission->invokeWebformElements($method, $webform_submission, $context1, $context2);
   }
 
   /****************************************************************************/
@@ -881,64 +878,9 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
   /****************************************************************************/
 
   /**
-   * Save webform submission data from the 'webform_submission_data' table.
-   *
-   * @param array $webform_submissions
-   *   An array of webform submissions.
-   */
-  protected function loadData(array &$webform_submissions) {
-    // Load webform submission data.
-    if ($sids = array_keys($webform_submissions)) {
-      /** @var \Drupal\Core\Database\StatementInterface $result */
-      $result = $this->database->select('webform_submission_data', 'sd')
-        ->fields('sd', ['webform_id', 'sid', 'name', 'property', 'delta', 'value'])
-        ->condition('sd.sid', $sids, 'IN')
-        ->orderBy('sd.sid', 'ASC')
-        ->orderBy('sd.name', 'ASC')
-        ->orderBy('sd.property', 'ASC')
-        ->orderBy('sd.delta', 'ASC')
-        ->execute();
-      $submissions_data = [];
-      while ($record = $result->fetchAssoc()) {
-        $sid = $record['sid'];
-        $name = $record['name'];
-
-        $elements = $webform_submissions[$sid]->getWebform()->getElementsInitializedFlattenedAndHasValue();
-        $element = (isset($elements[$name])) ? $elements[$name] : ['#webform_multiple' => FALSE, '#webform_composite' => FALSE];
-
-        if ($element['#webform_composite']) {
-          if ($element['#webform_multiple']) {
-            $submissions_data[$sid][$name][$record['delta']][$record['property']] = $record['value'];
-          }
-          else {
-            $submissions_data[$sid][$name][$record['property']] = $record['value'];
-          }
-        }
-        elseif ($element['#webform_multiple']) {
-          $submissions_data[$sid][$name][$record['delta']] = $record['value'];
-        }
-        else {
-          $submissions_data[$sid][$name] = $record['value'];
-        }
-      }
-
-      // Set webform submission data via setData().
-      foreach ($submissions_data as $sid => $submission_data) {
-        $webform_submissions[$sid]->setData($submission_data);
-        $webform_submissions[$sid]->setOriginalData($submission_data);
-      }
-    }
-  }
-
-  /**
-   * Save webform submission data to the 'webform_submission_data' table.
-   *
-   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
-   *   A webform submission.
-   * @param bool $delete_first
-   *   TRUE to delete any data first. For new submissions this is not needed.
+   * {@inheritdoc}
    */
-  protected function saveData(WebformSubmissionInterface $webform_submission, $delete_first = TRUE) {
+  public function saveData(WebformSubmissionInterface $webform_submission, $delete_first = TRUE) {
     // Get submission data rows.
     $data = $webform_submission->getData();
     $webform_id = $webform_submission->getWebform()->id();
@@ -1010,6 +952,56 @@ class WebformSubmissionStorage extends SqlContentEntityStorage implements Webfor
   }
 
   /**
+   * Save webform submission data from the 'webform_submission_data' table.
+   *
+   * @param array $webform_submissions
+   *   An array of webform submissions.
+   */
+  protected function loadData(array &$webform_submissions) {
+    // Load webform submission data.
+    if ($sids = array_keys($webform_submissions)) {
+      /** @var \Drupal\Core\Database\StatementInterface $result */
+      $result = $this->database->select('webform_submission_data', 'sd')
+        ->fields('sd', ['webform_id', 'sid', 'name', 'property', 'delta', 'value'])
+        ->condition('sd.sid', $sids, 'IN')
+        ->orderBy('sd.sid', 'ASC')
+        ->orderBy('sd.name', 'ASC')
+        ->orderBy('sd.property', 'ASC')
+        ->orderBy('sd.delta', 'ASC')
+        ->execute();
+      $submissions_data = [];
+      while ($record = $result->fetchAssoc()) {
+        $sid = $record['sid'];
+        $name = $record['name'];
+
+        $elements = $webform_submissions[$sid]->getWebform()->getElementsInitializedFlattenedAndHasValue();
+        $element = (isset($elements[$name])) ? $elements[$name] : ['#webform_multiple' => FALSE, '#webform_composite' => FALSE];
+
+        if ($element['#webform_composite']) {
+          if ($element['#webform_multiple']) {
+            $submissions_data[$sid][$name][$record['delta']][$record['property']] = $record['value'];
+          }
+          else {
+            $submissions_data[$sid][$name][$record['property']] = $record['value'];
+          }
+        }
+        elseif ($element['#webform_multiple']) {
+          $submissions_data[$sid][$name][$record['delta']] = $record['value'];
+        }
+        else {
+          $submissions_data[$sid][$name] = $record['value'];
+        }
+      }
+
+      // Set webform submission data via setData().
+      foreach ($submissions_data as $sid => $submission_data) {
+        $webform_submissions[$sid]->setData($submission_data);
+        $webform_submissions[$sid]->setOriginalData($submission_data);
+      }
+    }
+  }
+
+  /**
    * Delete webform submission data from the 'webform_submission_data' table.
    *
    * @param array $webform_submissions
diff --git a/src/WebformSubmissionStorageInterface.php b/src/WebformSubmissionStorageInterface.php
index 58d69c53..24c127ea 100644
--- a/src/WebformSubmissionStorageInterface.php
+++ b/src/WebformSubmissionStorageInterface.php
@@ -395,6 +395,25 @@ interface WebformSubmissionStorageInterface extends ContentEntityStorageInterfac
   public function purge($count);
 
   /****************************************************************************/
+  // Data handlers.
+  /****************************************************************************/
+
+  /**
+   * Save webform submission data to the 'webform_submission_data' table.
+   *
+   * This method is public the allow webform handler (ie remote posts) to
+   * update [webform_handler] tokens stored in the submission data.
+   *
+   * @param \Drupal\webform\WebformSubmissionInterface $webform_submission
+   *   A webform submission.
+   * @param bool $delete_first
+   *   TRUE to delete any data first. For new submissions this is not needed.
+   *
+   * @see \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler::remotePost
+   */
+  public function saveData(WebformSubmissionInterface $webform_submission, $delete_first = TRUE);
+
+  /****************************************************************************/
   // Log methods.
   /****************************************************************************/
 
diff --git a/src/WebformTokenManager.php b/src/WebformTokenManager.php
index 245a14e1..19063984 100644
--- a/src/WebformTokenManager.php
+++ b/src/WebformTokenManager.php
@@ -45,7 +45,7 @@ class WebformTokenManager implements WebformTokenManagerInterface {
     // Replace tokens within an array.
     if (is_array($text)) {
       foreach ($text as $key => $value) {
-        $text[$key] = $this->replace($value, $entity);
+        $text[$key] = $this->replace($value, $entity, $data, $options);
       }
       return $text;
     }
@@ -70,7 +70,7 @@ class WebformTokenManager implements WebformTokenManagerInterface {
   /**
    * {@inheritdoc}
    */
-  public function buildTreeLink(array $token_types = ['webform', 'webform_submission']) {
+  public function buildTreeLink(array $token_types = ['webform', 'webform_submission', 'webform_handler']) {
     if ($this->moduleHandler->moduleExists('token')) {
       // @todo Issue #2235581: Make Token Dialog support inserting in WYSIWYGs.
       return [
diff --git a/templates/webform-handler-remote-post-summary.html.twig b/templates/webform-handler-remote-post-summary.html.twig
index b8bb8ecd..87c9298f 100644
--- a/templates/webform-handler-remote-post-summary.html.twig
+++ b/templates/webform-handler-remote-post-summary.html.twig
@@ -11,7 +11,10 @@
  */
 #}
 {% if settings.debug %}<b class="color-error">{{ 'Debugging is enabled'|t }}</b><br />{% endif %}
-<b>{{ 'Insert URL:'|t }}</b> {{ settings.insert_url }}<br />
-{% if settings.update_url %}<b>{{ 'Update URL:'|t }}</b> {{ settings.update_url }}<br />{% endif %}
-{% if settings.delete_url %}<b>{{ 'Delete URL:'|t }}</b> {{ settings.delete_url }}<br />{% endif %}
+<b>{{ 'Completed URL:'|t }}</b> {{ settings.completed_url }}<br />
+{% if settings.updated_url %}<b>{{ 'Updated URL:'|t }}</b> {{ settings.updated_url }}<br />{% endif %}
+{% if settings.deleted_url %}<b>{{ 'Deleted URL:'|t }}</b> {{ settings.deleted_url }}<br />{% endif %}
+{% if settings.draft_url %}<b>{{ 'Draft URL:'|t }}</b> {{ settings.draft_url }}<br />{% endif %}
+{% if settings.converted_url %}<b>{{ 'Converted URL:'|t }}</b> {{ settings.converted_url }}<br />{% endif %}
+
 <b>{{ 'Type:'|t }}</b> {{ settings.type }}<br />
diff --git a/tests/modules/webform_test_handler/src/Controller/WebformTestRemotePostController.php b/tests/modules/webform_test_handler/src/Controller/WebformTestRemotePostController.php
deleted file mode 100644
index 288cce7d..00000000
--- a/tests/modules/webform_test_handler/src/Controller/WebformTestRemotePostController.php
+++ /dev/null
@@ -1,43 +0,0 @@
-<?php
-
-namespace Drupal\webform_test_handler\Controller;
-
-use Drupal\Core\Controller\ControllerBase;
-use Symfony\Component\HttpFoundation\JsonResponse;
-use Symfony\Component\HttpFoundation\Request;
-
-/**
- * Provides route responses for remote post tests.
- */
-class WebformTestRemotePostController extends ControllerBase {
-
-  /**
-   * Returns a webform confirmation page.
-   *
-   * @param \Symfony\Component\HttpFoundation\Request $request
-   *   The current request.
-   * @param string $type
-   *   Type of remote post request (insert, update, or delete)
-   *
-   * @return \Symfony\Component\HttpFoundation\JsonResponse
-   *   A JSON response with request status and message.
-   */
-  public function index(Request $request, $type) {
-    $post_data = $request->request->all();
-    if (strpos(print_r($post_data, TRUE), 'FAIL') !== FALSE) {
-      $json = [
-        'status' => 'fail',
-        'message' => (string) $this->t('Failed to process @type request.', ['@type' => $type]),
-      ];
-      return new JsonResponse($json, 500);
-    }
-    else {
-      $json = [
-        'status' => 'success',
-        'message' => (string) $this->t('Processed @type request.', ['@type' => $type]),
-      ];
-      return new JsonResponse($json, 200);
-    }
-  }
-
-}
diff --git a/tests/modules/webform_test_handler/webform_test_handler.install b/tests/modules/webform_test_handler/webform_test_handler.install
deleted file mode 100644
index 741c9d68..00000000
--- a/tests/modules/webform_test_handler/webform_test_handler.install
+++ /dev/null
@@ -1,33 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the Webform Test module.
- */
-
-use Drupal\webform\Entity\Webform;
-use Drupal\Core\Url;
-
-/**
- * Implements hook_install().
- */
-function webform_test_handler_install() {
-
-  // Update 'Test: Handler: Remote post' URLs.
-  /** @var \Drupal\webform\WebformInterface $remote_post_webform */
-  $remote_post_webform = Webform::load('test_handler_remote_post');
-
-  /** @var \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler $remote_post_handler */
-  $remote_post_handler = $remote_post_webform->getHandler('remote_post');
-  $remote_post_configuration = $remote_post_handler->getConfiguration();
-
-  // ISSUE: $base_url and $base_path are not working correctly so we are just
-  // going to use the front page.
-  $base_url = Url::fromRoute('<front>', [], ['absolute' => TRUE])->toString();
-  $remote_post_configuration['settings']['insert_url'] = $base_url . 'webform_test/remote_post/insert';
-  $remote_post_configuration['settings']['update_url'] = $base_url . 'webform_test/remote_post/update';
-  $remote_post_configuration['settings']['delete_url'] = $base_url . 'webform_test/remote_post/delete';
-  $remote_post_handler->setConfiguration($remote_post_configuration);
-
-  $remote_post_webform->save();
-}
diff --git a/tests/modules/webform_test_handler/webform_test_handler.routing.yml b/tests/modules/webform_test_handler/webform_test_handler.routing.yml
deleted file mode 100644
index be704133..00000000
--- a/tests/modules/webform_test_handler/webform_test_handler.routing.yml
+++ /dev/null
@@ -1,7 +0,0 @@
-webform_test_handler.remote_post:
-  path: '/webform_test/remote_post/{type}'
-  defaults:
-    _controller: '\Drupal\webform_test_handler\Controller\WebformTestRemotePostController::index'
-  requirements:
-    # Remote post mock handler does not require any access controls.
-    _access: 'TRUE'
diff --git a/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml b/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml
new file mode 100644
index 00000000..d59aa6e0
--- /dev/null
+++ b/tests/modules/webform_test_handler_remote_post/config/install/webform.webform.test_handler_remote_post.yml
@@ -0,0 +1,189 @@
+langcode: en
+status: open
+dependencies:
+  enforced:
+    module:
+      - webform_test_handler_remote_post
+open: null
+close: null
+uid: null
+template: false
+id: test_handler_remote_post
+title: 'Test: Handler: Remote post'
+description: 'Test remote post handler.'
+category: 'Test: Handler'
+elements: |
+  response_type:
+    '#title': 'Response type'
+    '#type': select
+    '#options':
+      200: '200 OK'
+      500: '500 Internal Server Error'
+      404: '404 Not Found'
+    '#default_value': 200
+  first_name:
+    '#title': 'First name'
+    '#type': textfield
+    '#required': true
+    '#default_value': John
+  last_name:
+    '#title': 'Last name'
+    '#type': textfield
+    '#required': true
+    '#default_value': Smith
+  confirmation_number:
+    '#title': 'Confirmation number'
+    '#type': value
+    '#value': '[webform_handler:remote_post:completed:confirmation_number]'
+css: ''
+javascript: ''
+settings:
+  ajax: false
+  page: true
+  page_submit_path: ''
+  page_confirm_path: ''
+  form_submit_once: false
+  form_exception_message: ''
+  form_open_message: ''
+  form_close_message: ''
+  form_previous_submissions: true
+  form_confidential: false
+  form_confidential_message: ''
+  form_convert_anonymous: true
+  form_prepopulate: false
+  form_prepopulate_source_entity: false
+  form_prepopulate_source_entity_required: false
+  form_prepopulate_source_entity_type: ''
+  form_reset: false
+  form_disable_autocomplete: false
+  form_novalidate: false
+  form_unsaved: false
+  form_disable_back: false
+  form_autofocus: false
+  form_details_toggle: false
+  submission_label: ''
+  submission_log: false
+  submission_user_columns: {  }
+  wizard_progress_bar: true
+  wizard_progress_pages: false
+  wizard_progress_percentage: false
+  wizard_start_label: ''
+  wizard_complete: true
+  wizard_complete_label: ''
+  preview: 0
+  preview_label: ''
+  preview_title: ''
+  preview_message: ''
+  preview_attributes: {  }
+  preview_excluded_elements: {  }
+  preview_exclude_empty: true
+  draft: all
+  draft_multiple: false
+  draft_auto_save: false
+  draft_saved_message: ''
+  draft_loaded_message: ''
+  confirmation_type: inline
+  confirmation_title: ''
+  confirmation_message: |
+    <p>Your confirmation number is [webform-submission:values:confirmation_number].</p>
+  confirmation_url: ''
+  confirmation_attributes: {  }
+  confirmation_back: true
+  confirmation_back_label: ''
+  confirmation_back_attributes: {  }
+  limit_total: null
+  limit_total_message: ''
+  limit_user: null
+  limit_user_message: ''
+  purge: none
+  purge_days: null
+  entity_limit_total: null
+  entity_limit_user: null
+  results_disabled: false
+  results_disabled_ignore: false
+  token_update: false
+access:
+  create:
+    roles:
+      - anonymous
+      - authenticated
+    users: {  }
+    permissions: {  }
+  view_any:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+  update_any:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+  delete_any:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+  purge_any:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+  view_own:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+  update_own:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+  delete_own:
+    roles: {  }
+    users: {  }
+    permissions: {  }
+handlers:
+  remote_post:
+    id: remote_post
+    label: 'Remote post'
+    handler_id: remote_post
+    status: true
+    weight: 1
+    settings:
+      type: x-www-form-urlencoded
+      excluded_data:
+        serial: serial
+        sid: sid
+        uuid: uuid
+        token: token
+        uri: uri
+        created: created
+        completed: completed
+        changed: changed
+        in_draft: in_draft
+        current_page: current_page
+        remote_addr: remote_addr
+        uid: uid
+        langcode: langcode
+        webform_id: webform_id
+        entity_type: entity_type
+        entity_id: entity_id
+        sticky: sticky
+        notes: notes
+        confirmation_number: confirmation_number
+      custom_data: |
+        custom_data: true
+      custom_options: |
+        headers:
+          custom_header: 'true'
+      debug: true
+      completed_url: 'http://webform-test-handler-remote-post/completed'
+      completed_custom_data: |
+        custom_completed: true
+      updated_url: 'http://webform-test-handler-remote-post/updated'
+      updated_custom_data: |
+        custom_updated: true
+      deleted_url: 'http://webform-test-handler-remote-post/deleted'
+      deleted_custom_data: |
+        custom_deleted: true
+      draft_url: 'http://webform-test-handler-remote-post/draft'
+      draft_custom_data: |
+        custom_draft: true
+      converted_url: 'http://webform-test-handler-remote-post/converted'
+      converted_custom_data: |
+        custom_converted: true
diff --git a/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php b/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php
new file mode 100644
index 00000000..8bcc615b
--- /dev/null
+++ b/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClient.php
@@ -0,0 +1,60 @@
+<?php
+
+namespace Drupal\webform_test_handler_remote_post;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Component\Serialization\Json;
+use Drupal\Component\Utility\Random;
+use GuzzleHttp\Client;
+use GuzzleHttp\Psr7\Response;
+
+/**
+ * Extend Guzzle client so that we can override remote posts.
+ */
+class WebformTestHandlerRemotePostClient extends Client {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function request($method, $uri = '', array $options = []) {
+    if (strpos($uri, 'http://webform-test-handler-remote-post/') === FALSE) {
+      return parent::request($method, $uri, $options);
+    }
+
+    $operation = str_replace('http://webform-test-handler-remote-post/', '', $uri);
+    $params = (isset($options['json'])) ? $options['json'] : $options['form_params'];
+    $response_type = $params['response_type'];
+    $random = new Random();
+    // Handle 404 errors.
+    switch ($response_type) {
+      // 404 Not Found.
+      case 404:
+        return new Response(404, [], 'File not found');
+
+      // 500 Internal Server Error.
+      case 500:
+        $status = 500;
+        $headers = ['Content-Type' =>['application/json']];
+        $json = [
+          'status' => 'fail',
+          'message' => (string) new FormattableMarkup('Failed to process @type request.', ['@type' => $operation]),
+          'options' => $options,
+        ];
+        return new Response($status, $headers, Json::encode($json));
+
+      // 200 OK.
+      case 200:
+      default:
+        $status = 200;
+        $headers = ['Content-Type' =>['application/json']];
+        $json = [
+          'status' => 'success',
+          'message' => (string) new FormattableMarkup('Processed @type request.', ['@type' => $operation]),
+          'options' => $options,
+          'confirmation_number' => $random->name(20, TRUE),
+        ];
+        return new Response($status, $headers, Json::encode($json));
+    }
+  }
+
+}
diff --git a/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClientFactory.php b/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClientFactory.php
new file mode 100644
index 00000000..aa7bf4ff
--- /dev/null
+++ b/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostClientFactory.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace Drupal\webform_test_handler_remote_post;
+
+use Drupal\Component\Utility\NestedArray;
+use Drupal\Core\Http\ClientFactory;
+use Drupal\Core\Site\Settings;
+
+/**
+ * Extend Drupal client so that we can override Guzzel client.
+ *
+ * @see \Drupal\Core\Http\ClientFactory::fromOptions
+ */
+class WebformTestHandlerRemotePostClientFactory extends ClientFactory {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fromOptions(array $config = []) {
+    $default_config = [
+      'verify' => TRUE,
+      'timeout' => 30,
+      'headers' => [
+        'User-Agent' => 'Drupal/' . \Drupal::VERSION . ' (+https://www.drupal.org/) ' . \GuzzleHttp\default_user_agent(),
+      ],
+      'handler' => $this->stack,
+      'proxy' => [
+        'http' => NULL,
+        'https' => NULL,
+        'no' => [],
+      ]
+    ];
+    $config = NestedArray::mergeDeep($default_config, Settings::get('http_client_config', []), $config);
+    return new WebformTestHandlerRemotePostClient($config);
+  }
+
+}
diff --git a/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostServiceProvider.php b/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostServiceProvider.php
new file mode 100755
index 00000000..d549c660
--- /dev/null
+++ b/tests/modules/webform_test_handler_remote_post/src/WebformTestHandlerRemotePostServiceProvider.php
@@ -0,0 +1,21 @@
+<?php
+
+namespace Drupal\webform_test_handler_remote_post;
+
+use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+
+/**
+ * Overrides the http client factory service.
+ */
+class WebformTestHandlerRemotePostServiceProvider extends ServiceProviderBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alter(ContainerBuilder $container) {
+    $definition = $container->getDefinition('http_client_factory');
+    $definition->setClass('Drupal\webform_test_handler_remote_post\WebformTestHandlerRemotePostClientFactory');
+  }
+
+}
diff --git a/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.features.yml b/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.features.yml
new file mode 100644
index 00000000..f32a5804
--- /dev/null
+++ b/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.features.yml
@@ -0,0 +1 @@
+true
\ No newline at end of file
diff --git a/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml b/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml
new file mode 100644
index 00000000..1a36a80b
--- /dev/null
+++ b/tests/modules/webform_test_handler_remote_post/webform_test_handler_remote_post.info.yml
@@ -0,0 +1,7 @@
+name: 'Webform module remote post tests'
+type: module
+description: 'Support module for Webform module remote post testing.'
+package: Testing
+core: 8.x
+dependencies:
+  - 'drupal:webform'
diff --git a/webform.install b/webform.install
index 17c29e02..eb892b3b 100644
--- a/webform.install
+++ b/webform.install
@@ -1029,3 +1029,41 @@ function webform_update_8070() {
   _webform_update_admin_settings();
 }
 
+/**
+ * Issue #2898424: Improve Remote Post.
+ */
+function webform_update_8071() {
+  $settings_mapping = [
+    'insert_url' => 'completed_url',
+    'insert_custom_data' => 'completed_custom_data',
+    'update_url' => 'updated_url',
+    'update_custom_data' => 'updated_custom_data',
+    'delete_url' => 'deleted_url',
+    'delete_custom_data' => 'deleted_custom_data',
+  ];
+
+  /** @var \Drupal\webform\WebformInterface[] $webforms */
+  $webforms = Webform::loadMultiple();
+  foreach ($webforms as $webform) {
+    $has_remote_post = FALSE;
+    $handlers = $webform->getHandlers();
+    foreach ($handlers as $handler) {
+      if ($handler instanceof \Drupal\webform\Plugin\WebformHandler\RemotePostWebformHandler) {
+        $has_remote_post = TRUE;
+        $configuration = $handler->getConfiguration();
+        foreach ($configuration['settings'] as $key => $value) {
+          if (isset($settings_mapping[$key])) {
+            $configuration['settings'][$settings_mapping[$key]] = $value;
+            unset($configuration['settings'][$key]);
+          }
+        }
+        $configuration['settings'] += $handler->defaultConfiguration();
+        $handler->setConfiguration($configuration);
+      }
+    }
+    if ($has_remote_post) {
+      $webform->save();
+    }
+  }
+
+}
diff --git a/webform.tokens.inc b/webform.tokens.inc
index edc02994..a85985b1 100644
--- a/webform.tokens.inc
+++ b/webform.tokens.inc
@@ -5,10 +5,11 @@
  * Builds placeholder replacement tokens for webforms and submissions.
  */
 
-use Drupal\Core\Render\Markup;
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Render\MarkupInterface;
 use Drupal\Core\Datetime\Entity\DateFormat;
 use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\Render\Markup;
 use Drupal\user\Entity\User;
 use Drupal\webform\Utility\WebformDateHelper;
 use Drupal\webform\Plugin\WebformElementManagerInterface;
@@ -241,7 +242,7 @@ function webform_token_info() {
   $tokens['webform'] = $webform;
 
   /****************************************************************************/
-  // Webform.
+  // Webform role.
   /****************************************************************************/
 
   $roles = \Drupal::config('webform.settings')->get('mail.roles');
@@ -268,6 +269,32 @@ function webform_token_info() {
   }
 
   /****************************************************************************/
+  // Webform handler.
+  /****************************************************************************/
+
+  $types['webform_handler'] = [
+    'name' => t('Webform handler'),
+    'description' => t('Tokens related to the current webform handler.'),
+    'needs-data' => 'webform_handler',
+  ];
+
+  $webform_handler = [];
+  $webform_handler['id'] = [
+    'name' => t('Webform handler ID'),
+    'description' => Markup::create((string) t('Webform handler response tokens.') . '<br/>' .
+      t("Replace the 'id:?' with...") . '<br />' .
+      '<ul>' .
+      '<li>webform_handler_id:state:key</li>' .
+      '<li>webform_handler_id:state:key1:key2</li>' .
+      '</ul>' .
+      t("For example, to display a remote post's confirmation number you would use the [webform_handler:remote_post:completed:confirmation_number] token.")
+    ),
+    'dynamic' => TRUE,
+  ];
+
+  $tokens['webform_handler'] = $webform_handler;
+
+  /****************************************************************************/
 
   return ['types' => $types, 'tokens' => $tokens];
 }
@@ -333,6 +360,22 @@ function webform_tokens($type, $tokens, array $data, array $options, BubbleableM
     }
 
   }
+  elseif ($type == 'webform_handler') {
+    if (isset($data['webform_handler'])) {
+      $webform_handler = $data['webform_handler'];
+      foreach ($tokens as $name => $original) {
+        $parents = explode(':', $name);
+        $value = NestedArray::getValue($webform_handler, $parents, $key_exists);
+        $replacements[$original] = ($key_exists) ? $value : $original;
+      }
+    }
+    else {
+      // Preserve all [webform_handler] tokens.
+      foreach ($tokens as $name => $original) {
+        $replacements[$original] = $original;
+      }
+    }
+  }
   elseif ($type == 'webform_submission' && !empty($data['webform_submission'])) {
     /** @var \Drupal\webform\Plugin\WebformElementManagerInterface $element_manager */
     $element_manager = \Drupal::service('plugin.manager.webform.element');
