diff --git a/src/Hook/WebformRulesHooks.php b/src/Hook/WebformRulesHooks.php
new file mode 100644
index 0000000..b9245ce
--- /dev/null
+++ b/src/Hook/WebformRulesHooks.php
@@ -0,0 +1,170 @@
+<?php
+
+namespace Drupal\webform_rules\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Component\Utility\Html;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for webform_rules.
+ */
+class WebformRulesHooks
+{
+    use StringTranslationTrait;
+    /**
+     * @file
+     * Adds rules integration for webform submissions.
+     */
+    /**
+     * Implements hook_webform_submission_insert().
+     *
+     * @param $node
+     *   The webform node.
+     * @param $submission
+     *   The webform submission.
+     */
+    #[Hook('webform_submission_insert')]
+    public static function webformSubmissionInsert($node, $submission)
+    {
+        // Invoke event.
+        webform_rules_rules_invoke_event($submission, $node, 'insert');
+    }
+    /**
+     * Implements hook_webform_submission_update().
+     *
+     * @param $node
+     *   The webform node.
+     * @param $submission
+     *   The webform submission.
+     */
+    #[Hook('webform_submission_update')]
+    public static function webformSubmissionUpdate($node, $submission)
+    {
+        // Invoke event.
+        webform_rules_rules_invoke_event($submission, $node, 'update');
+    }
+    /**
+     * Implements hook_webform_submission_delete().
+     *
+     * @param $node
+     *   The webform node.
+     * @param $submission
+     *   The webform submission.
+     */
+    #[Hook('webform_submission_delete')]
+    public static function webformSubmissionDelete($node, $submission)
+    {
+        // Invoke event.
+        webform_rules_rules_invoke_event($submission, $node, 'delete');
+    }
+    /**
+     * Implements hook_token_info().
+     */
+    #[Hook('token_info')]
+    public function tokenInfo()
+    {
+        $types['webform'] = [
+            'name' => $this->t('Webform data'),
+            'description' => $this->t('Tokens related to data submitted by webforms.'),
+        ];
+        $webform['sid'] = [
+            'name' => $this->t('Submission Id'),
+            'description' => $this->t('The unique identifier of the submission.'),
+        ];
+        $webform['data'] = [
+            'name' => $this->t('Submitted data'),
+            'description' => $this->t('The submitted webform data.'),
+        ];
+        $webform['data-raw'] = [
+            'name' => $this->t('Raw submitted data'),
+            'description' => $this->t('The unfiltered submitted webform data.'),
+        ];
+        $webform['{component}-title'] = [
+            'name' => $this->t('Component title'),
+            'description' => $this->t('The title of the selected component, e.g. "email-title".'),
+        ];
+        $webform['{component}-value'] = [
+            'name' => $this->t('Component value'),
+            'description' => $this->t('The value of the selected component, e.g. "email-value".'),
+        ];
+        $webform['{component}-value-html'] = [
+            'name' => $this->t('Component value as html'),
+            'description' => $this->t('The value of the selected component rendered as html, e.g. "email-value-html".'),
+        ];
+        $webform['{component}-value-raw'] = [
+            'name' => $this->t('Raw component value'),
+            'description' => $this->t('The raw value of the selected component, e.g. "email-value-raw". However this is not cleaned up by check_plain(). This is raw user input so take care if you use this somewhere else.'),
+        ];
+        $webform['{component}-display'] = [
+            'name' => $this->t('Component display'),
+            'description' => $this->t('Title and value of the selected component, e.g. "email-display".'),
+        ];
+        $webform['{component}-display-html'] = [
+            'name' => $this->t('Component display as html'),
+            'description' => $this->t('Title and value of the selected component rendered as html, e.g. "email-display-html".'),
+        ];
+        return [
+            'types' => $types,
+            'tokens' => [
+                'webform' => $webform,
+            ],
+        ];
+    }
+    /**
+     * Implements hook_tokens().
+     */
+    #[Hook('tokens')]
+    public static function tokens($type, $tokens, array $data = [
+    ], array $options = [
+    ])
+    {
+        $replacements = [
+        ];
+        if ($type == 'webform') {
+            $component_names = array_keys($data[$type]['components']);
+            foreach ($tokens as $name => $original) {
+                if ($name == 'sid') {
+                    $replacements[$original] = $data[$type]['sid'];
+                } elseif ($name == 'data') {
+                    $replacements[$original] = webform_rules_prepare_component_value($data[$type]);
+                } elseif ($name == 'data-raw') {
+                    $replacements[$original] = webform_rules_prepare_component_value($data[$type], TRUE);
+                } else {
+                    // Split token name to get the components name.
+                    $token = explode('-', $name);
+                    $component_name = array_shift($token);
+                    if (!in_array($component_name, $component_names)) {
+                        continue;
+                    }
+                    // Join token name (without component name).
+                    $token = implode('-', $token);
+                    // Webform component.
+                    $component = $data[$type]['components'][$component_name]['component'];
+                    $component_value = $data[$type]['components'][$component_name]['value'];
+                    switch ($token) {
+                        case 'title':
+                            $replacements[$original] = $component['name'];
+                            break;
+                        case 'display':
+                            $replacements[$original] = webform_rules_render_component($component, $component_value);
+                            break;
+                        case 'display-html':
+                            $replacements[$original] = webform_rules_render_component($component, $component_value, 'html');
+                            break;
+                        case 'value':
+                            $replacements[$original] = webform_rules_render_component($component, $component_value, 'text', FALSE);
+                            break;
+                        case 'value-html':
+                            $replacements[$original] = webform_rules_render_component($component, $component_value, 'html', FALSE);
+                            break;
+                        case 'value-raw':
+                            $replacements[$original] = webform_rules_prepare_component_value($component_value, TRUE);
+                            break;
+                    }
+                }
+            }
+        }
+        return $replacements;
+    }
+}
diff --git a/src/Hook/WebformRulesRulesHooks.php b/src/Hook/WebformRulesRulesHooks.php
new file mode 100644
index 0000000..e4fefb6
--- /dev/null
+++ b/src/Hook/WebformRulesRulesHooks.php
@@ -0,0 +1,99 @@
+<?php
+
+namespace Drupal\webform_rules\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+/**
+ * Hook implementations for webform_rules.
+ */
+class WebformRulesRulesHooks
+{
+    use StringTranslationTrait;
+    /**
+     * Implements hook_rules_action_info().
+     *
+     */
+    #[Hook('rules_action_info')]
+    public function rulesActionInfo()
+    {
+        $defaults = [
+            'group' => $this->t('Webform'),
+            'access callback' => 'rules_node_admin_access',
+            'parameter' => [
+                'entity' => [
+                    'type' => 'node',
+                    'label' => $this->t('Webform'),
+                    'description' => $this->t('A webform-enabled node.'),
+                    'save' => TRUE,
+                    'optional' => TRUE,
+                ],
+                'selected_webform' => [
+                    'type' => 'list<text>',
+                    'label' => $this->t('Webforms'),
+                    'options list' => 'webform_rules_get_webforms_as_options',
+                    'description' => $this->t('List of webforms to open.'),
+                    'restriction' => 'input',
+                    'optional' => TRUE,
+                ],
+            ],
+            'callbacks' => [
+                'validate' => 'webform_rules_webform_statuschange_validate',
+            ],
+        ];
+        $actions = [
+            'webform_open' => $defaults + [
+                'label' => $this->t('Open webforms'),
+                'base' => 'webform_rules_webform_open',
+            ],
+            'webform_close' => $defaults + [
+                'label' => $this->t('Close webforms'),
+                'base' => 'webform_rules_webform_close',
+            ],
+            'webform_submissions_load' => [
+                'label' => $this->t('Fetch webform submissions'),
+                'base' => $this->t('webform_rules_submissions_load'),
+                'group' => $this->t('Webform'),
+                'access callback' => 'rules_node_admin_access',
+                'parameter' => [
+                    'nid' => [
+                        'type' => 'integer',
+                        'label' => $this->t('Node ID'),
+                        'description' => $this->t('The ID of the webform node to load the submission for.'),
+                    ],
+                    'sid' => [
+                        'type' => 'integer',
+                        'label' => $this->t('Submission ID'),
+                        'description' => $this->t('The ID of a webform submission. If omitted all submissions of the specified node ID will be fetched.'),
+                        'optional' => TRUE,
+                    ],
+                ],
+                'provides' => [
+                    'submissions' => [
+                        'label' => $this->t('Fetched submissions'),
+                        'type' => 'list<list>',
+                    ],
+                ],
+            ],
+        ];
+        // Modify description of closing action.
+        $actions['webform_close']['parameter']['selected_webform']['description'] = $this->t('The name of the webforms to close.');
+        return $actions;
+    }
+    /**
+     * Implements hook_rules_data_info().
+     */
+    #[Hook('rules_data_info', module: 'webform')]
+    public function dataInfo()
+    {
+        return [
+            'webform_data' => [
+                'label' => $this->t('webform data'),
+                'group' => $this->t('Webform'),
+                'token type' => 'webform',
+                'property info' => [
+                ],
+            ],
+        ];
+    }
+}
diff --git a/src/Plugin/RulesAction/CloseWebformsAction.php b/src/Plugin/RulesAction/CloseWebformsAction.php
index 19913c3..51079c9 100644
--- a/src/Plugin/RulesAction/CloseWebformsAction.php
+++ b/src/Plugin/RulesAction/CloseWebformsAction.php
@@ -27,7 +27,7 @@ class CloseWebformsAction extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+  public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
     $access = $object->status->access('edit', $account, TRUE)
       ->andIf($object->access('update', $account, TRUE));
 
diff --git a/src/Plugin/RulesAction/FetchWebformSubmissionsAction.php b/src/Plugin/RulesAction/FetchWebformSubmissionsAction.php
index 0ca26c2..15f5ea2 100644
--- a/src/Plugin/RulesAction/FetchWebformSubmissionsAction.php
+++ b/src/Plugin/RulesAction/FetchWebformSubmissionsAction.php
@@ -26,7 +26,7 @@ class FetchWebformSubmissionsAction extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+  public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
     $access = $object->status->access('edit', $account, TRUE)
       ->andIf($object->access('update', $account, TRUE));
 
diff --git a/src/Plugin/RulesAction/OpenWebformsAction.php b/src/Plugin/RulesAction/OpenWebformsAction.php
index d25b43b..b311f28 100644
--- a/src/Plugin/RulesAction/OpenWebformsAction.php
+++ b/src/Plugin/RulesAction/OpenWebformsAction.php
@@ -27,7 +27,7 @@ class OpenWebformsAction extends ActionBase {
   /**
    * {@inheritdoc}
    */
-  public function access($object, AccountInterface $account = NULL, $return_as_object = FALSE) {
+  public function access($object, ?AccountInterface $account = NULL, $return_as_object = FALSE) {
     $access = $object->status->access('edit', $account, TRUE)
       ->andIf($object->access('update', $account, TRUE));
 
diff --git a/webform_rules.module b/webform_rules.module
index 4c58a20..d7819cf 100644
--- a/webform_rules.module
+++ b/webform_rules.module
@@ -1,5 +1,10 @@
 <?php
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\webform_rules\Hook\WebformRulesHooks;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Component\Utility\Html;
+
 /**
  * @file
  * Adds rules integration for webform submissions.
@@ -13,9 +18,9 @@
  * @param $submission
  *   The webform submission.
  */
+#[LegacyHook]
 function webform_rules_webform_submission_insert($node, $submission) {
-  // Invoke event.
-  webform_rules_rules_invoke_event($submission, $node, 'insert');
+  \Drupal::service(WebformRulesHooks::class)->webformSubmissionInsert($node, $submission);
 }
 
 /**
@@ -26,9 +31,9 @@ function webform_rules_webform_submission_insert($node, $submission) {
  * @param $submission
  *   The webform submission.
  */
+#[LegacyHook]
 function webform_rules_webform_submission_update($node, $submission) {
-  // Invoke event.
-  webform_rules_rules_invoke_event($submission, $node, 'update');
+  \Drupal::service(WebformRulesHooks::class)->webformSubmissionUpdate($node, $submission);
 }
 
 /**
@@ -39,15 +44,15 @@ function webform_rules_webform_submission_update($node, $submission) {
  * @param $submission
  *   The webform submission.
  */
+#[LegacyHook]
 function webform_rules_webform_submission_delete($node, $submission) {
-  // Invoke event.
-  webform_rules_rules_invoke_event($submission, $node, 'delete');
+  \Drupal::service(WebformRulesHooks::class)->webformSubmissionDelete($node, $submission);
 }
 
 /**
  * Implements of hook_form_alter().
  */
-function webform_rules_form_alter(&$form, \Drupal\Core\Form\FormStateInterface $form_state, $form_id) {
+function webform_rules_form_alter(&$form, FormStateInterface $form_state, $form_id) {
   if (strpos($form_id, 'webform_client_form_') !== FALSE) {
     // Add custom submit handler to webform form.
     $form['#submit'][] = 'webform_rules_client_form_submit';
@@ -153,110 +158,19 @@ function webform_rules_rules_invoke_event($submission, $node, $op = 'insert') {
 /**
  * Implements hook_token_info().
  */
-function webform_rules_token_info() {
-  $types['webform'] = array(
-    'name' => t('Webform data'),
-    'description' => t('Tokens related to data submitted by webforms.'),
-  );
-
-  $webform['sid'] = array(
-    'name' => t('Submission Id'),
-    'description' => t('The unique identifier of the submission.'),
-  );
-  $webform['data'] = array(
-    'name' => t('Submitted data'),
-    'description' => t('The submitted webform data.'),
-  );
-  $webform['data-raw'] = array(
-    'name' => t('Raw submitted data'),
-    'description' => t('The unfiltered submitted webform data.'),
-  );
-  $webform['{component}-title'] = array(
-    'name' => t('Component title'),
-    'description' => t('The title of the selected component, e.g. "email-title".'),
-  );
-  $webform['{component}-value'] = array(
-    'name' => t('Component value'),
-    'description' => t('The value of the selected component, e.g. "email-value".'),
-  );
-  $webform['{component}-value-html'] = array(
-    'name' => t('Component value as html'),
-    'description' => t('The value of the selected component rendered as html, e.g. "email-value-html".'),
-  );
-  $webform['{component}-value-raw'] = array(
-    'name' => t('Raw component value'),
-    'description' => t('The raw value of the selected component, e.g. "email-value-raw". However this is not cleaned up by check_plain(). This is raw user input so take care if you use this somewhere else.'),
-  );
-  $webform['{component}-display'] = array(
-    'name' => t('Component display'),
-    'description' => t('Title and value of the selected component, e.g. "email-display".'),
-  );
-  $webform['{component}-display-html'] = array(
-    'name' => t('Component display as html'),
-    'description' => t('Title and value of the selected component rendered as html, e.g. "email-display-html".'),
-  );
-
-  return array(
-    'types' => $types,
-    'tokens' => array(
-      'webform' => $webform,
-    ),
-  );
+#[LegacyHook]
+function webform_rules_token_info()
+{
+    return \Drupal::service(WebformRulesHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
-function webform_rules_tokens($type, $tokens, array $data = array(), array $options = array()) {
-  $replacements = array();
-  if ($type == 'webform') {
-    $component_names = array_keys($data[$type]['components']);
-    foreach ($tokens as $name => $original) {
-      if ($name == 'sid') {
-        $replacements[$original] = $data[$type]['sid'];
-      }
-      elseif ($name == 'data') {
-        $replacements[$original] = webform_rules_prepare_component_value($data[$type]);
-      }
-      elseif ($name == 'data-raw') {
-        $replacements[$original] = webform_rules_prepare_component_value($data[$type], TRUE);
-      }
-      else {
-        // Split token name to get the components name.
-        $token = explode('-', $name);
-        $component_name = array_shift($token);
-        if (!in_array($component_name, $component_names)) {
-          continue;
-        }
-        // Join token name (without component name).
-        $token = implode('-', $token);
-        // Webform component.
-        $component = $data[$type]['components'][$component_name]['component'];
-        $component_value = $data[$type]['components'][$component_name]['value'];
-        switch ($token) {
-          case 'title':
-            $replacements[$original] = $component['name'];
-            break;
-          case 'display':
-            $replacements[$original] = webform_rules_render_component($component, $component_value);
-            break;
-          case 'display-html':
-            $replacements[$original] = webform_rules_render_component($component, $component_value, 'html');
-            break;
-          case 'value':
-            $replacements[$original] = webform_rules_render_component($component, $component_value, 'text', FALSE);
-            break;
-          case 'value-html':
-            $replacements[$original] = webform_rules_render_component($component, $component_value, 'html', FALSE);
-            break;
-          case 'value-raw':
-            $replacements[$original] = webform_rules_prepare_component_value($component_value, TRUE);
-            break;
-        }
-      }
-    }
-  }
-  return $replacements;
+#[LegacyHook]
+function webform_rules_tokens($type, $tokens, array $data = array(), array $options = array())
+{
+    return \Drupal::service(WebformRulesHooks::class)->tokens($type, $tokens, $data, $options);
 }
 
 /**
@@ -277,7 +191,7 @@ function webform_rules_prepare_component_value($component_value, $raw = FALSE) {
       return $component_value;
     case 'resource':
     case 'string':
-      return $raw ? $component_value : \Drupal\Component\Utility\Html::escape($component_value);
+      return $raw ? $component_value : Html::escape($component_value);
     case 'array':
       // If the array is empty or if it has sequential whole number keys
       // starting with 0, it's not associative so we can go ahead and
@@ -324,7 +238,7 @@ function webform_rules_render_component($component, $value, $format = 'text', $t
   $display_element = webform_component_invoke($component['type'], 'display', $component, $value, $format);
   $display_element['#parents'] = array('submitted', $component['form_key']);
   if (!isset($display_element['#id'])) {
-    $display_element['#id'] = \Drupal\Component\Utility\Html::cleanCssIdentifier('edit-' . implode('-', $display_element['#parents']));
+    $display_element['#id'] = Html::cleanCssIdentifier('edit-' . implode('-', $display_element['#parents']));
   }
   if (!$title) {
     $display_element['#title'] = NULL;
diff --git a/webform_rules.rules.inc b/webform_rules.rules.inc
index a0b32e3..47e9c68 100644
--- a/webform_rules.rules.inc
+++ b/webform_rules.rules.inc
@@ -1,5 +1,8 @@
 <?php
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\webform_rules\Hook\WebformRulesRulesHooks;
+
 /**
  * @file
  * Functions for rules integration.
@@ -88,85 +91,18 @@ function webform_rules_rules_condition_info() {
  * Implements hook_rules_action_info().
  *
  */
-function webform_rules_rules_action_info() {
-  $defaults = array(
-    'group' => t('Webform'),
-    'access callback' => 'rules_node_admin_access',
-    'parameter' => array(
-      'entity' => array(
-        'type' => 'node',
-        'label' => t('Webform'),
-        'description' => t('A webform-enabled node.'),
-        'save' => TRUE,
-        'optional' => TRUE,
-      ),
-      'selected_webform' => array(
-        'type' => 'list<text>',
-        'label' => t('Webforms'),
-        'options list' => 'webform_rules_get_webforms_as_options',
-        'description' => t('List of webforms to open.'),
-        'restriction' => 'input',
-        'optional' => TRUE,
-      ),
-    ),
-    'callbacks' => array(
-      'validate' => 'webform_rules_webform_statuschange_validate',
-    ),
-  );
-  $actions = array(
-    'webform_open' => $defaults + array(
-      'label' => t('Open webforms'),
-      'base' => 'webform_rules_webform_open',
-    ),
-    'webform_close' => $defaults + array(
-      'label' => t('Close webforms'),
-      'base' => 'webform_rules_webform_close',
-    ),
-    'webform_submissions_load' => array(
-      'label' => t('Fetch webform submissions'),
-      'base' => t('webform_rules_submissions_load'),
-      'group' => t('Webform'),
-      'access callback' => 'rules_node_admin_access',
-      'parameter' => array(
-        'nid' => array(
-          'type' => 'integer',
-          'label' => t('Node ID'),
-          'description' => t('The ID of the webform node to load the submission for.'),
-        ),
-        'sid' => array(
-          'type' => 'integer',
-          'label' => t('Submission ID'),
-          'description' => t('The ID of a webform submission. If omitted all submissions of the specified node ID will be fetched.'),
-          'optional' => TRUE,
-        ),
-      ),
-      'provides' => array(
-        'submissions' => array(
-          'label' => t('Fetched submissions'),
-          'type' => 'list<list>',
-        ),
-      ),
-    ),
-  );
-
-  // Modify description of closing action.
-  $actions['webform_close']['parameter']['selected_webform']['description'] = t('The name of the webforms to close.');
-
-  return $actions;
+#[LegacyHook]
+function webform_rules_rules_action_info()
+{
+    return \Drupal::service(WebformRulesRulesHooks::class)->rulesActionInfo();
 }
 
 /**
  * Implements hook_rules_data_info().
  */
+#[LegacyHook]
 function webform_rules_data_info() {
-  return array(
-    'webform_data' => array(
-      'label' => t('webform data'),
-      'group' => t('Webform'),
-      'token type' => 'webform',
-      'property info' => array(),
-    ),
-  );
+  return \Drupal::service(WebformRulesRulesHooks::class)->dataInfo();
 }
 
 /**
diff --git a/webform_rules.services.yml b/webform_rules.services.yml
new file mode 100644
index 0000000..78cbb1c
--- /dev/null
+++ b/webform_rules.services.yml
@@ -0,0 +1,9 @@
+
+services:
+  Drupal\webform_rules\Hook\WebformRulesHooks:
+    class: Drupal\webform_rules\Hook\WebformRulesHooks
+    autowire: true
+
+  Drupal\webform_rules\Hook\WebformRulesRulesHooks:
+    class: Drupal\webform_rules\Hook\WebformRulesRulesHooks
+    autowire: true
