You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2023/07/27 16:12:54 UTC

[skywalking] branch master updated: Support multiple configs for each hook types and specifying the hooks in the alarm rule. (#11137)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/skywalking.git


The following commit(s) were added to refs/heads/master by this push:
     new 9c2376a960 Support multiple configs for each hook types and specifying the hooks in the alarm rule. (#11137)
9c2376a960 is described below

commit 9c2376a960e82dafd1b4f50caad2048f2b22be52
Author: Wan Kai <wa...@foxmail.com>
AuthorDate: Fri Jul 28 00:12:47 2023 +0800

    Support multiple configs for each hook types and specifying the hooks in the alarm rule. (#11137)
    
    * [Breaking change] Adjust the structure of hooks in the `alarm-settings.yml`. Support multiple configs for each hook types and specifying the hooks in the alarm rule.
---
 dist-material/alarm-settings.yml                   |  10 +-
 docs/en/changes/changes.md                         |   1 +
 docs/en/setup/backend/backend-alarm.md             | 189 +++++++----
 ...positeAlarmRule.java => AlarmHookSettings.java} |  29 +-
 .../core/alarm/provider/AlarmHooksType.java}       |  19 +-
 .../oap/server/core/alarm/provider/AlarmRule.java  |  29 +-
 .../core/alarm/provider/AlarmRulesWatcher.java     |  37 +-
 .../core/alarm/provider/CompositeAlarmRule.java    |   2 +
 .../alarm/provider/CompositeRuleEvaluator.java     |   1 +
 .../server/core/alarm/provider/NotifyHandler.java  |   1 +
 .../oap/server/core/alarm/provider/Rules.java      |  31 +-
 .../server/core/alarm/provider/RulesReader.java    | 375 ++++++++++++++-------
 .../server/core/alarm/provider/RunningRule.java    |   4 +
 .../provider/dingtalk/DingtalkHookCallback.java    |  29 +-
 .../alarm/provider/dingtalk/DingtalkSettings.java  |  16 +-
 .../provider/discord/DiscordHookCallback.java      |  28 +-
 .../alarm/provider/discord/DiscordSettings.java    |  16 +-
 .../alarm/provider/feishu/FeishuHookCallback.java  |  28 +-
 .../core/alarm/provider/feishu/FeishuSettings.java |  14 +-
 .../core/alarm/provider/grpc/GRPCAlarmSetting.java |  12 +-
 .../core/alarm/provider/grpc/GRPCCallback.java     |  98 ++++--
 .../provider/pagerduty/PagerDutyHookCallback.java  |  34 +-
 .../provider/pagerduty/PagerDutySettings.java      |  17 +-
 .../core/alarm/provider/slack/SlackSettings.java   |  17 +-
 .../alarm/provider/slack/SlackhookCallback.java    |  33 +-
 .../provider/{ => webhook}/WebhookCallback.java    |  23 +-
 .../WebhookSettings.java}                          |  32 +-
 .../alarm/provider/wechat/WechatHookCallback.java  |  27 +-
 .../core/alarm/provider/wechat/WechatSettings.java |  17 +-
 .../alarm/provider/welink/WeLinkHookCallback.java  |  33 +-
 .../core/alarm/provider/welink/WeLinkSettings.java |  28 +-
 .../server/core/alarm/provider/AlarmCoreTest.java  |   1 -
 .../core/alarm/provider/AlarmRulesWatcherTest.java |   9 +-
 .../alarm/provider/CompositeRuleEvaluatorTest.java |  15 +-
 .../core/alarm/provider/RulesReaderTest.java       |  42 ++-
 .../dingtalk/DingtalkHookCallbackTest.java         |  23 +-
 .../provider/feishu/FeishuHookCallbackTest.java    |  34 +-
 .../alarm/provider/grpc/GRPChookCallbackTest.java  |  37 +-
 .../pagerduty/PagerDutyHookCallbackTest.java       |  41 ++-
 .../SlackHookCallbackTest.java}                    |  41 ++-
 .../{ => webhook}/WebhookCallbackTest.java         |  14 +-
 .../provider/wechat/WechatHookCallbackTest.java    |  12 +-
 .../provider/welink/WeLinkHookCallbackTest.java    |  32 +-
 .../src/test/resources/alarm-settings.yml          | 189 +++++++----
 .../oap/server/core/alarm/AlarmCallback.java       |  18 +-
 .../oap/server/core/alarm/AlarmMessage.java        |   3 +
 .../src/main/resources/alarm-settings.yml          | 206 ++++++-----
 test/e2e-v2/cases/alarm/alarm-settings.yml         |  13 +-
 48 files changed, 1278 insertions(+), 682 deletions(-)

diff --git a/dist-material/alarm-settings.yml b/dist-material/alarm-settings.yml
index c5e0646093..d955a6f081 100644
--- a/dist-material/alarm-settings.yml
+++ b/dist-material/alarm-settings.yml
@@ -80,7 +80,11 @@ rules:
 #    silence-period: 5
 #    message: Response time of endpoint {name} is more than 1000ms in 2 minutes of last 10 minutes
 
-webhooks:
-#  - http://127.0.0.1/notify/
-#  - http://127.0.0.1/go-wechat/
+#hooks:
+#  webhook:
+#    default:
+#      is-default: true
+#      urls:
+#        - http://127.0.0.1/notify/
+#        - http://127.0.0.1/go-wechat/
 
diff --git a/docs/en/changes/changes.md b/docs/en/changes/changes.md
index 27cefe9a4c..b625fc3adf 100644
--- a/docs/en/changes/changes.md
+++ b/docs/en/changes/changes.md
@@ -55,6 +55,7 @@
 * Move created the latest index before retrieval indexes by aliases to avoid the 404 exception. This just prevents some interference from manual operations.
 * Add more Go VM metrics, as new skywalking-go agent provided since its 0.2 release.
 * Add component ID for Lock (ID=5016).
+* [Breaking change] Adjust the structure of hooks in the `alarm-settings.yml`. Support multiple configs for each hook types and specifying the hooks in the alarm rule.
 * Bump up Armeria to 1.24.3.
 
 #### UI
diff --git a/docs/en/setup/backend/backend-alarm.md b/docs/en/setup/backend/backend-alarm.md
index f93856fbd4..6e81eeba24 100644
--- a/docs/en/setup/backend/backend-alarm.md
+++ b/docs/en/setup/backend/backend-alarm.md
@@ -5,8 +5,7 @@ Alerting kernel is an in-memory, time-window based queue.
 The alerting core is driven by a collection of rules defined in `config/alarm-settings.yml.`
 There are three parts to alerting rule definitions.
 1. [alerting rules](#rules). They define how metrics alerting should be triggered and what conditions should be considered.
-1. [Webhooks](#webhook). The list of web service endpoints, which should be called after an alerting is triggered.
-1. [gRPCHook](#grpchook). The host and port of the remote gRPC method, which should be called after an alerting is triggered.
+1. [hooks](#hooks). The list of hooks, which should be called after an alerting is triggered.
 
 ## Entity name
 Defines the relation between scope and entity name.
@@ -47,6 +46,9 @@ For example, in **percentile**, `value1` is the threshold of P50, and `-, -, val
 - **Period**. The size of metrics cache in minutes for checking the alarm conditions. This is a time window that corresponds to the backend deployment env time.
 - **Count**. Within a period window, if the number of times which **value** goes over the threshold (based on OP) reaches `count`, then an alarm will be sent.
 - **Only as condition**. Indicates if the rule can send notifications or if it simply serves as a condition of the composite rule.
+- **Hooks**. Binding the specific names of the hooks when the alarm is triggered.
+  The name format is `{hookType}.{hookName}` (slack.custom1 e.g.) and must be defined in the `hooks` section of the `alarm-settings.yml` file.
+  If the hook name is not specified, the global hook will be used.
 - **Silence period**. After the alarm is triggered at Time-N (TN), there will be silence during the **TN -> TN + period**.
 By default, it works in the same manner as **period**. The same Alarm (having the same ID in the same metrics name) may only be triggered once within a period. 
 
@@ -69,6 +71,10 @@ A composite rule is made up of the following elements:
 - **Expression**. Specifies how to compose rules, and supports `&&`, `||`, and `()`.
 - **Message**. The notification message to be sent out when the rule is triggered.
 - **Tags**. Tags are key/value pairs that are attached to alarms. Tags are used to specify distinguishing attributes of alarms that are meaningful and relevant to users.
+- **Hooks**. Binding the specific names of the hooks when the alarm is triggered.
+  The name format is `{hookType}.{hookName}` (slack.custom1 e.g.) and must be defined in the `hooks` section of the `alarm-settings.yml` file.
+  If the hook name is not specified, the global hooks will be used.
+
 ```yaml
 rules:
   # Rule unique name, must be ended with `_rule`.
@@ -123,6 +129,9 @@ rules:
     silence-period: 5
     message: The request number of entity {name} non-200 status is more than expected.
     only-as-condition: false
+    hooks:
+      - "slack.custom1"
+      - "pagerduty.custom1"
 composite-rules:
   comp_rule:
     # Must satisfied percent rule and resp time rule 
@@ -130,6 +139,10 @@ composite-rules:
     message: Service {name} successful rate is less than 80% and P50 of response time is over 1000ms
     tags:
       level: CRITICAL
+    hooks:
+      - "slack.default"
+      - "slack.custom1"
+      - "pagerduty.custom1"
 ```
 
 
@@ -152,7 +165,44 @@ Currently, metrics from the **Service**, **Service Instance**, **Endpoint**, **S
 
 Submit an issue or a pull request if you want to support any other scopes in Alarm.
 
-## Webhook
+## Hooks
+Hooks are a way to send alarm messages to the outside world. SkyWalking supports multiple hooks of the same type, each hook can support different configurations. 
+For example, you can configure two Slack hooks, one named `default` and set `is-default: true` means this hook will apply on all `Alarm Rules` **without config** `hooks`.
+Another named `custom1` will only apply on the `Alarm Rules` which **with config** `hooks` and include the name `slack.custom1`.
+
+```yaml
+hooks:
+  slack:
+    # default here is just a name, set the field 'is-default: true' if this notification hook is expected to be default globally.
+    default:
+      # If true, this hook will apply on all rules, unless a rule has its own specific hook. Could have more than one default hooks in the same hook type.
+      is-default: true 
+      text-template: |-
+        {
+          "type": "section",
+          "text": {
+            "type": "mrkdwn",
+            "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+          }
+        }
+      webhooks:
+        - https://hooks.slack.com/services/x/y/zssss
+    custom1:
+      text-template: |-
+        {
+          "type": "section",
+          "text": {
+            "type": "mrkdwn",
+            "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+          }
+        }
+      webhooks:
+        - https://hooks.slack.com/services/x/y/custom1
+```
+
+Currently, SkyWalking supports the following hook types:
+
+### Webhook
 The Webhook requires the peer to be a web container. The alarm message will be sent through HTTP post by `application/json` content type. The JSON format is based on `List<org.apache.skywalking.oap.server.core.alarm.AlarmMessage>` with the following key information:
 - **scopeId**, **scope**. All scopes are defined in `org.apache.skywalking.oap.server.core.source.DefaultScopeDefine`.
 - **name**. Target scope entity name. Please follow the [entity name definitions](#entity-name).
@@ -194,7 +244,7 @@ See the following example:
 }]
 ```
 
-## gRPCHook
+### gRPC
 The alarm message will be sent through remote gRPC method by `Protobuf` content type. 
 The message contains key information which are defined in `oap-server/server-alarm-plugin/src/main/proto/alarm-hook.proto`.
 
@@ -223,120 +273,134 @@ message KeyStringValuePair {
 }
 ```
 
-## Slack Chat Hook
+### Slack Chat
 Follow the [Getting Started with Incoming Webhooks guide](https://api.slack.com/messaging/webhooks) and create new Webhooks.
 
 The alarm message will be sent through HTTP post by `application/json` content type if you have configured Slack Incoming Webhooks as follows:
 ```yml
-slackHooks:
-  textTemplate: |-
-    {
-      "type": "section",
-      "text": {
-        "type": "mrkdwn",
-        "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+slack:
+  default:
+    is-default: true
+    text-template: |-
+      {
+        "type": "section",
+        "text": {
+          "type": "mrkdwn",
+          "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+        }
       }
-    }
-  webhooks:
+    webhooks:
     - https://hooks.slack.com/services/x/y/z
 ```
 
-## WeChat Hook
+### WeChat
 Note that only the WeChat Company Edition (WeCom) supports WebHooks. To use the WeChat WebHook, follow the [Wechat Webhooks guide](https://work.weixin.qq.com/help?doc_id=13376).
 The alarm message will be sent through HTTP post by `application/json` content type after you have set up Wechat Webhooks as follows:
 ```yml
-wechatHooks:
-  textTemplate: |-
-    {
-      "msgtype": "text",
-      "text": {
-        "content": "Apache SkyWalking Alarm: \n %s."
+wechat:
+  default:
+    is-default: true
+    text-template: |-
+      {
+        "msgtype": "text",
+        "text": {
+          "content": "Apache SkyWalking Alarm: \n %s."
+        }
       }
-    }
-  webhooks:
+    webhooks:
     - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key
 ```
 
-## DingTalk Hook
+### DingTalk
 Follow the [Dingtalk Webhooks guide](https://ding-doc.dingtalk.com/doc#/serverapi2/qf2nxq/uKPlK) and create new Webhooks.
 You can configure an optional secret for an individual webhook URL for security purposes.
 The alarm message will be sent through HTTP post by `application/json` content type if you have configured DingTalk Webhooks as follows:
 ```yml
-dingtalkHooks:
-  textTemplate: |-
-    {
-      "msgtype": "text",
-      "text": {
-        "content": "Apache SkyWalking Alarm: \n %s."
+dingtalk:
+  default:
+    is-default: true
+    text-template: |-
+      {
+        "msgtype": "text",
+        "text": {
+          "content": "Apache SkyWalking Alarm: \n %s."
+        }
       }
-    }
-  webhooks:
+    webhooks:
     - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token
       secret: dummysecret
 ```
 
-## Feishu Hook
+### Feishu
 Follow the [Feishu Webhooks guide](https://www.feishu.cn/hc/zh-cn/articles/360024984973) and create new Webhooks.
 You can configure an optional secret for an individual webhook URL for security purposes.
 If you want to direct a text to a user, you can configure `ats`, which is Feishu's user_id and separated by "," .
 The alarm message will be sent through HTTP post by `application/json` content type if you have configured Feishu Webhooks as follows:
 ```yml
-feishuHooks:
-  textTemplate: |-
-    {
-      "msg_type": "text",
-      "content": {
-        "text": "Apache SkyWalking Alarm: \n %s."
-      },
-      "ats":"feishu_user_id_1,feishu_user_id_2"
-    }
-  webhooks:
+feishu:
+  default:
+    is-default: true
+    text-template: |-
+      {
+        "msg_type": "text",
+        "content": {
+          "text": "Apache SkyWalking Alarm: \n %s."
+        },
+        "ats":"feishu_user_id_1,feishu_user_id_2"
+      }
+    webhooks:
     - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
       secret: dummysecret
 ```
 
-## WeLink Hook
+### WeLink
 Follow the [WeLink Webhooks guide](https://open.welink.huaweicloud.com/apiexplorer/#/apiexplorer?type=internal&method=POST&path=/welinkim/v1/im-service/chat/group-chat) and create new Webhooks.
 The alarm message will be sent through HTTP post by `application/json` content type if you have configured WeLink Webhooks as follows:
 ```yml
-welinkHooks:
-  textTemplate: "Apache SkyWalking Alarm: \n %s."
-  webhooks:
+welink:
+  default:
+    is-default: true
+    text-template: "Apache SkyWalking Alarm: \n %s."
+    webhooks:
     # you may find your own client_id and client_secret in your app, below are dummy, need to change.
-    - client_id: "dummy_client_id"
-      client_secret: dummy_secret_key
-      access_token_url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
-      message_url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
+    - client-id: "dummy_client_id"
+      client-secret: dummy_secret_key
+      access-token-url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
+      message-url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
       # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
-      group_ids: "dummy_group_id"
+      group-ids: "dummy_group_id"
       # make a name you like for the robot, it will display in group
-      robot_name: robot
+      robot-name: robot
 ```
 
 
-## PagerDuty Hook
+### PagerDuty
 The PagerDuty hook is based on [Events API v2](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview).
 
 Follow the [Getting Started](https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTgw-events-api-v2-overview#getting-started) section to create an **Events API v2** integration on your PagerDuty service and copy the integration key.
 
 Then configure as follows:
 ```yml
-pagerDutyHooks:
-  textTemplate: "Apache SkyWalking Alarm: \n %s."
-  integrationKeys:
+pagerduty:
+  default:
+    is-default: true
+    text-template: "Apache SkyWalking Alarm: \n %s."
+    integration-keys:
     - 5c6d805c9dcf4e03d09dfa81e8789ba1
 ```
 
 You can also configure multiple integration keys.
 
-## Discord Hook
+### Discord
 Follow the [Discord Webhooks guide](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks) and create a new webhook.
 
 Then configure as follows:
 ```yml
-discordHooks:
-  textTemplate: "Apache SkyWalking Alarm: \n %s."
-  webhooks:
+discord:
+  default:
+    is-default: true
+    text-template: "Apache SkyWalking Alarm: \n %s."
+    webhooks:
     - url: https://discordapp.com/api/webhooks/1008166889777414645/8e0Am4Zb-YGbBqqbiiq0jSHPTEEaHa4j1vIC-zSSm231T8ewGxgY0_XUYpY-k1nN4HBl
       username: robot
 ```
@@ -368,4 +432,5 @@ the sliding window will be destroyed and re-created, causing the Alarm of this s
 | Count                | count                      | int            |                    |
 | Only as condition    | only-as-condition          | boolean        |                    |
 | Silence period       | silence-period             | int            |                    |
-| Message              | message                    | string         |                    |
\ No newline at end of file
+| Message              | message                    | string         |                    |
+| Hooks                | hooks                      | string array   |                    |
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmHookSettings.java
similarity index 69%
copy from oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java
copy to oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmHookSettings.java
index 116a19253c..efb0d7e1a2 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmHookSettings.java
@@ -18,24 +18,19 @@
 
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
-import lombok.Setter;
-import lombok.ToString;
+import lombok.RequiredArgsConstructor;
+import org.apache.skywalking.oap.server.core.Const;
 
-import java.util.Map;
+@RequiredArgsConstructor
+public abstract class AlarmHookSettings {
+    private final String name;
+    @Getter
+    private final AlarmHooksType type;
+    @Getter
+    private final boolean isDefault;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-@Setter
-@Getter
-@ToString
-public class CompositeAlarmRule {
-    private String alarmRuleName;
-    private String expression;
-    private String message;
-    private Map<String, String> tags;
+    public String getFormattedName() {
+        return this.type.name() + Const.POINT + name;
+    }
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmHooksType.java
similarity index 74%
copy from oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java
copy to oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmHooksType.java
index d4730876a0..0e9c0276a9 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmHooksType.java
@@ -16,13 +16,16 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm;
+package org.apache.skywalking.oap.server.core.alarm.provider;
 
-import java.util.List;
-
-/**
- * Alarm call back will be called by alarm implementor, after it decided alarm should be sent.
- */
-public interface AlarmCallback {
-    void doAlarm(List<AlarmMessage> alarmMessage) throws Exception;
+public enum AlarmHooksType {
+    webhook,
+    gRPC,
+    wechat,
+    slack,
+    dingtalk,
+    feishu,
+    welink,
+    discord,
+    pagerduty
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java
index 70a9c43ff5..0da0698ea3 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRule.java
@@ -20,9 +20,10 @@ package org.apache.skywalking.oap.server.core.alarm.provider;
 
 import java.util.ArrayList;
 import java.util.Map;
-import java.util.Objects;
+import java.util.Set;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
+import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
@@ -34,6 +35,7 @@ import lombok.ToString;
 @Setter
 @Getter
 @ToString
+@EqualsAndHashCode
 public class AlarmRule {
     private String alarmRuleName;
     private String metricsName;
@@ -53,28 +55,5 @@ public class AlarmRule {
     private String message;
     private boolean onlyAsCondition;
     private Map<String, String> tags;
-
-    @Override
-    public boolean equals(final Object o) {
-        if (this == o) {
-            return true;
-        }
-
-        if (o == null || getClass() != o.getClass()) {
-            return false;
-        }
-
-        final AlarmRule alarmRule = (AlarmRule) o;
-
-        return period == alarmRule.period && count == alarmRule.count && silencePeriod == alarmRule.silencePeriod && Objects
-            .equals(alarmRuleName, alarmRule.alarmRuleName) && Objects.equals(metricsName, alarmRule.metricsName) && Objects
-            .equals(includeNames, alarmRule.includeNames) && Objects.equals(excludeNames, alarmRule.excludeNames) && Objects
-            .equals(threshold, alarmRule.threshold) && Objects.equals(op, alarmRule.op) && Objects.equals(message, alarmRule.message)
-            && Objects.equals(includeNamesRegex, alarmRule.includeNamesRegex) && Objects.equals(excludeNamesRegex, alarmRule.excludeNamesRegex);
-    }
-
-    @Override
-    public int hashCode() {
-        return Objects.hash(alarmRuleName, metricsName, includeNames, excludeNames, threshold, op, period, count, silencePeriod, message);
-    }
+    private Set<String> hooks;
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
index 19938ba33f..f27ea3f475 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcher.java
@@ -35,6 +35,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSetting
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
 import org.apache.skywalking.oap.server.core.alarm.provider.pagerduty.PagerDutySettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.webhook.WebhookSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSettings;
 import org.apache.skywalking.oap.server.library.module.ModuleProvider;
@@ -117,39 +118,39 @@ public class AlarmRulesWatcher extends ConfigChangeWatcher {
         return this.rules.getCompositeRules();
     }
 
-    public List<String> getWebHooks() {
-        return this.rules.getWebhooks();
+    public Map<String, WebhookSettings> getWebHooks() {
+        return this.rules.getWebhookSettingsMap();
     }
 
-    public GRPCAlarmSetting getGrpchookSetting() {
-        return this.rules.getGrpchookSetting();
+    public Map<String, GRPCAlarmSetting> getGrpchookSetting() {
+        return this.rules.getGrpcAlarmSettingMap();
     }
 
-    public SlackSettings getSlackSettings() {
-        return this.rules.getSlacks();
+    public Map<String, SlackSettings> getSlackSettings() {
+        return this.rules.getSlackSettingsMap();
     }
 
-    public WechatSettings getWechatSettings() {
-        return this.rules.getWecchats();
+    public Map<String, WechatSettings> getWechatSettings() {
+        return this.rules.getWechatSettingsMap();
     }
 
-    public DingtalkSettings getDingtalkSettings() {
-        return this.rules.getDingtalks();
+    public Map<String, DingtalkSettings> getDingtalkSettings() {
+        return this.rules.getDingtalkSettingsMap();
     }
 
-    public FeishuSettings getFeishuSettings() {
-        return this.rules.getFeishus();
+    public Map<String, FeishuSettings> getFeishuSettings() {
+        return this.rules.getFeishuSettingsMap();
     }
 
-    public WeLinkSettings getWeLinkSettings() {
-        return this.rules.getWelinks();
+    public Map<String, WeLinkSettings> getWeLinkSettings() {
+        return this.rules.getWeLinkSettingsMap();
     }
 
-    public PagerDutySettings getPagerDutySettings() {
-        return this.rules.getPagerDutySettings();
+    public Map<String, PagerDutySettings> getPagerDutySettings() {
+        return this.rules.getPagerDutySettingsMap();
     }
 
-    public DiscordSettings getDiscordSettings() {
-        return this.rules.getDiscordSettings();
+    public Map<String, DiscordSettings> getDiscordSettings() {
+        return this.rules.getDiscordSettingsMap();
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java
index 116a19253c..3a8c1050b5 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeAlarmRule.java
@@ -18,6 +18,7 @@
 
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
+import java.util.Set;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Getter;
@@ -38,4 +39,5 @@ public class CompositeAlarmRule {
     private String expression;
     private String message;
     private Map<String, String> tags;
+    private Set<String> hooks;
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluator.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluator.java
index 7fa761d7b8..f0d0440b8e 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluator.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluator.java
@@ -88,6 +88,7 @@ public class CompositeRuleEvaluator {
                     message.setAlarmMessage(alarmMessage);
                     message.setPeriod(headMsg.getPeriod());
                     message.setTags(compositeAlarmRule.getTags().entrySet().stream().map(e -> new Tag(e.getKey(), e.getValue())).collect(Collectors.toList()));
+                    message.getHooks().addAll(compositeAlarmRule.getHooks());
                     compositeRuleMessages.add(message);
                 }
             });
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
index ee14741476..79984b2fa3 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/NotifyHandler.java
@@ -37,6 +37,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuHookCal
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.pagerduty.PagerDutyHookCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackhookCallback;
+import org.apache.skywalking.oap.server.core.alarm.provider.webhook.WebhookCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatHookCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkHookCallback;
 import org.apache.skywalking.oap.server.core.analysis.IDManager;
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
index 537bab99c1..9179d9ea26 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/Rules.java
@@ -19,7 +19,9 @@
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import lombok.Getter;
 import lombok.Setter;
 import lombok.ToString;
@@ -29,6 +31,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSetting
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
 import org.apache.skywalking.oap.server.core.alarm.provider.pagerduty.PagerDutySettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.webhook.WebhookSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSettings;
 
@@ -37,20 +40,28 @@ import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSetting
 @ToString
 public class Rules {
     private List<AlarmRule> rules;
-    private List<String> webhooks;
-    private GRPCAlarmSetting grpchookSetting;
-    private SlackSettings slacks;
-    private WechatSettings wecchats;
+    private Map<String, WebhookSettings> webhookSettingsMap;
+    private Map<String, GRPCAlarmSetting> grpcAlarmSettingMap;
+    private Map<String, SlackSettings> slackSettingsMap;
+    private Map<String, WechatSettings> wechatSettingsMap;
     private List<CompositeAlarmRule> compositeRules;
-    private DingtalkSettings dingtalks;
-    private FeishuSettings feishus;
-    private WeLinkSettings welinks;
-    private PagerDutySettings pagerDutySettings;
-    private DiscordSettings discordSettings;
+    private Map<String, DingtalkSettings> dingtalkSettingsMap;
+    private Map<String, FeishuSettings> feishuSettingsMap;
+    private Map<String, WeLinkSettings> weLinkSettingsMap;
+    private Map<String, PagerDutySettings> pagerDutySettingsMap;
+    private Map<String, DiscordSettings> discordSettingsMap;
 
     public Rules() {
         this.rules = new ArrayList<>();
-        this.webhooks = new ArrayList<>();
+        this.webhookSettingsMap = new HashMap<>();
         this.compositeRules = new ArrayList<>();
+        this.grpcAlarmSettingMap = new HashMap<>();
+        this.slackSettingsMap = new HashMap<>();
+        this.wechatSettingsMap = new HashMap<>();
+        this.dingtalkSettingsMap = new HashMap<>();
+        this.feishuSettingsMap = new HashMap<>();
+        this.weLinkSettingsMap = new HashMap<>();
+        this.pagerDutySettingsMap = new HashMap<>();
+        this.discordSettingsMap = new HashMap<>();
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
index 924c04894f..4c18623951 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReader.java
@@ -23,13 +23,15 @@ import java.io.Reader;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.stream.Collectors;
-
 import org.apache.skywalking.oap.server.core.alarm.provider.discord.DiscordSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.pagerduty.PagerDutySettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.webhook.WebhookSettings;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 import org.apache.skywalking.oap.server.core.alarm.provider.dingtalk.DingtalkSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSettings;
@@ -47,6 +49,8 @@ import org.yaml.snakeyaml.constructor.SafeConstructor;
  */
 public class RulesReader {
     private Map yamlData;
+    private final Set<String> defaultHooks = new HashSet<>();
+    private final Set<String> allHooks = new HashSet<>();
 
     public RulesReader(InputStream inputStream) {
         Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions()));
@@ -65,17 +69,10 @@ public class RulesReader {
         Rules rules = new Rules();
 
         if (Objects.nonNull(yamlData)) {
+            // Should read hooks config first.
+            readHooksConfig(rules);
             readRulesConfig(rules);
-            readWebHookConfig(rules);
-            readGrpcConfig(rules);
-            readSlackConfig(rules);
-            readWechatConfig(rules);
             readCompositeRuleConfig(rules);
-            readDingtalkConfig(rules);
-            readFeishuConfig(rules);
-            readWeLinkConfig(rules);
-            readPagerDutyConfig(rules);
-            readDiscordConfig(rules);
         }
         return rules;
     }
@@ -121,83 +118,156 @@ public class RulesReader {
                         (String) settings.getOrDefault("message", "Alarm caused by Rule " + alarmRule
                                 .getAlarmRuleName()));
                 alarmRule.setTags((Map) settings.getOrDefault("tags", new HashMap<String, String>()));
+
+                Set<String> specificHooks = new HashSet<>((ArrayList) settings.getOrDefault("hooks", new ArrayList<>()));
+                checkSpecificHooks(alarmRule.getAlarmRuleName(), specificHooks);
+                alarmRule.setHooks(specificHooks);
+                // If no specific hooks, use global hooks.
+                if (alarmRule.getHooks().isEmpty()) {
+                    alarmRule.getHooks().addAll(defaultHooks);
+                }
                 rules.getRules().add(alarmRule);
             }
         });
     }
 
+    private void readHooksConfig(Rules rules) {
+        Map hooks = (Map) yamlData.getOrDefault("hooks", Collections.EMPTY_MAP);
+        if (CollectionUtils.isEmpty(hooks)) {
+            return;
+        }
+        readWebHookConfig(hooks, rules);
+        readGrpcConfig(hooks, rules);
+        readSlackConfig(hooks, rules);
+        readWechatConfig(hooks, rules);
+        readDingtalkConfig(hooks, rules);
+        readFeishuConfig(hooks, rules);
+        readWeLinkConfig(hooks, rules);
+        readPagerDutyConfig(hooks, rules);
+        readDiscordConfig(hooks, rules);
+    }
+
     /**
      * Read web hook config
      */
-    private void readWebHookConfig(Rules rules) {
-        List webhooks = (List) yamlData.get("webhooks");
-        if (webhooks != null) {
-            rules.setWebhooks(new ArrayList<>());
-            webhooks.forEach(url -> {
-                rules.getWebhooks().add((String) url);
-            });
+    @SuppressWarnings("unchecked")
+    private void readWebHookConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.webhook.name());
+        if (configs == null) {
+            return;
         }
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            WebhookSettings settings = new WebhookSettings(
+                k.toString(), AlarmHooksType.webhook, (Boolean) config.getOrDefault("is-default", false));
+
+            List<String> urls = (List<String>) config.get("urls");
+            if (urls != null) {
+                settings.getUrls().addAll(urls);
+            }
+            rules.getWebhookSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read grpc hook config into {@link GRPCAlarmSetting}
      */
-    private void readGrpcConfig(Rules rules) {
-        Map grpchooks = (Map) yamlData.get("gRPCHook");
-        if (grpchooks != null) {
-            GRPCAlarmSetting grpcAlarmSetting = new GRPCAlarmSetting();
-            Object targetHost = grpchooks.get("target_host");
+    @SuppressWarnings("unchecked")
+    private void readGrpcConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.gRPC.name());
+        if (configs == null) {
+            return;
+        }
+        configs.forEach((k, v) -> {
+            Map config = (Map) v;
+            GRPCAlarmSetting setting = new GRPCAlarmSetting(
+                k.toString(), AlarmHooksType.gRPC, (Boolean) config.getOrDefault("is-default", false));
+
+            Object targetHost = config.get("target-host");
             if (targetHost != null) {
-                grpcAlarmSetting.setTargetHost((String) targetHost);
+                setting.setTargetHost((String) targetHost);
             }
 
-            Object targetPort = grpchooks.get("target_port");
+            Object targetPort = config.get("target-port");
             if (targetPort != null) {
-                grpcAlarmSetting.setTargetPort((Integer) targetPort);
+                setting.setTargetPort((Integer) targetPort);
             }
 
-            rules.setGrpchookSetting(grpcAlarmSetting);
-        }
+            rules.getGrpcAlarmSettingMap().put(setting.getFormattedName(), setting);
+
+            if (setting.isDefault()) {
+                this.defaultHooks.add(setting.getFormattedName());
+            }
+            this.allHooks.add(setting.getFormattedName());
+        });
     }
 
     /**
      * Read slack hook config into {@link SlackSettings}
      */
-    private void readSlackConfig(Rules rules) {
-        Map slacks = (Map) yamlData.get("slackHooks");
-        if (slacks != null) {
-            SlackSettings slackSettings = new SlackSettings();
-            Object textTemplate = slacks.getOrDefault("textTemplate", "");
-            slackSettings.setTextTemplate((String) textTemplate);
-
-            List<String> slackWebhooks = (List<String>) slacks.get("webhooks");
-            if (slackWebhooks != null) {
-                slackSettings.getWebhooks().addAll(slackWebhooks);
-            }
-            rules.setSlacks(slackSettings);
+    @SuppressWarnings("unchecked")
+    private void readSlackConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.slack.name());
+        if (configs == null) {
+            return;
         }
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            SlackSettings settings = new SlackSettings(
+                k.toString(), AlarmHooksType.slack, (Boolean) config.getOrDefault("is-default", false));
+
+            Object textTemplate = config.getOrDefault("text-template", "");
+            settings.setTextTemplate((String) textTemplate);
+
+            List<String> webhooks = (List<String>) config.get("webhooks");
+            if (webhooks != null) {
+                settings.getWebhooks().addAll(webhooks);
+            }
+            rules.getSlackSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read wechat hook config into {@link WechatSettings}
      */
-    private void readWechatConfig(Rules rules) {
-        Map wechatConfig = (Map) yamlData.get("wechatHooks");
-        if (wechatConfig != null) {
-            WechatSettings wechatSettings = new WechatSettings();
-            Object textTemplate = wechatConfig.getOrDefault("textTemplate", "");
-            wechatSettings.setTextTemplate((String) textTemplate);
-            List<String> wechatWebhooks = (List<String>) wechatConfig.get("webhooks");
-            if (wechatWebhooks != null) {
-                wechatSettings.getWebhooks().addAll(wechatWebhooks);
-            }
-            rules.setWecchats(wechatSettings);
+    @SuppressWarnings("unchecked")
+    private void readWechatConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.wechat.name());
+        if (configs == null) {
+            return;
         }
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            WechatSettings settings = new WechatSettings(
+                k.toString(), AlarmHooksType.wechat, (Boolean) config.getOrDefault("is-default", false));
+
+            Object textTemplate = config.getOrDefault("text-template", "");
+            settings.setTextTemplate((String) textTemplate);
+
+            List<String> webhooks = (List<String>) config.get("webhooks");
+            if (webhooks != null) {
+                settings.getWebhooks().addAll(webhooks);
+            }
+            rules.getWechatSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read composite rule config into {@link CompositeAlarmRule}
      */
+    @SuppressWarnings("unchecked")
     private void readCompositeRuleConfig(Rules rules) {
         Map compositeRulesData = (Map) yamlData.get("composite-rules");
         if (compositeRulesData == null) {
@@ -217,6 +287,14 @@ public class RulesReader {
                 compositeAlarmRule.setMessage(
                         (String) settings.getOrDefault("message", "Alarm caused by Rule " + ruleName));
                 compositeAlarmRule.setTags((Map) settings.getOrDefault("tags", new HashMap<String, String>(0)));
+
+                Set<String> specificHooks = new HashSet<>((ArrayList) settings.getOrDefault("hooks", new ArrayList<>()));
+                checkSpecificHooks(compositeAlarmRule.getAlarmRuleName(), specificHooks);
+                compositeAlarmRule.setHooks(specificHooks);
+                // If no specific hooks, use global hooks.
+                if (compositeAlarmRule.getHooks().isEmpty()) {
+                    compositeAlarmRule.getHooks().addAll(defaultHooks);
+                }
                 rules.getCompositeRules().add(compositeAlarmRule);
             }
         });
@@ -225,109 +303,168 @@ public class RulesReader {
     /**
      * Read dingtalk hook config into {@link DingtalkSettings}
      */
-    private void readDingtalkConfig(Rules rules) {
-        Map dingtalkConfig = (Map) yamlData.get("dingtalkHooks");
-        if (dingtalkConfig != null) {
-            DingtalkSettings dingtalkSettings = new DingtalkSettings();
-            Object textTemplate = dingtalkConfig.getOrDefault("textTemplate", "");
-            dingtalkSettings.setTextTemplate((String) textTemplate);
-            List<Map<String, Object>> wechatWebhooks = (List<Map<String, Object>>) dingtalkConfig.get("webhooks");
-            if (wechatWebhooks != null) {
-                wechatWebhooks.forEach(wechatWebhook -> {
-                    Object secret = wechatWebhook.getOrDefault("secret", "");
-                    Object url = wechatWebhook.getOrDefault("url", "");
-                    dingtalkSettings.getWebhooks().add(new DingtalkSettings.WebHookUrl((String) secret, (String) url));
+    @SuppressWarnings("unchecked")
+    private void readDingtalkConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.dingtalk.name());
+        if (configs == null) {
+            return;
+        }
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            DingtalkSettings settings = new DingtalkSettings(
+                k.toString(), AlarmHooksType.dingtalk, (Boolean) config.getOrDefault("is-default", false));
+
+            Object textTemplate = config.getOrDefault("text-template", "");
+            settings.setTextTemplate((String) textTemplate);
+
+            List<Map<String, Object>> webhooks = (List<Map<String, Object>>) config.get("webhooks");
+            if (webhooks != null) {
+                webhooks.forEach(webhook -> {
+                    Object secret = webhook.getOrDefault("secret", "");
+                    Object url = webhook.getOrDefault("url", "");
+                    settings.getWebhooks().add(new DingtalkSettings.WebHookUrl((String) secret, (String) url));
                 });
             }
-            rules.setDingtalks(dingtalkSettings);
-        }
+            rules.getDingtalkSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read feishu hook config into {@link FeishuSettings}
      */
-    private void readFeishuConfig(Rules rules) {
-        Map feishuConfig = (Map) yamlData.get("feishuHooks");
-        if (feishuConfig != null) {
-            FeishuSettings feishuSettings = new FeishuSettings();
-            Object textTemplate = feishuConfig.getOrDefault("textTemplate", "");
-            feishuSettings.setTextTemplate((String) textTemplate);
-            List<Map<String, Object>> wechatWebhooks = (List<Map<String, Object>>) feishuConfig.get("webhooks");
-            if (wechatWebhooks != null) {
-                wechatWebhooks.forEach(wechatWebhook -> {
-                    Object secret = wechatWebhook.getOrDefault("secret", "");
-                    Object url = wechatWebhook.getOrDefault("url", "");
-                    feishuSettings.getWebhooks().add(new FeishuSettings.WebHookUrl((String) secret, (String) url));
+    @SuppressWarnings("unchecked")
+    private void readFeishuConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.feishu.name());
+        if (configs == null) {
+            return;
+        }
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            FeishuSettings settings = new FeishuSettings(
+                k.toString(), AlarmHooksType.feishu, (Boolean) config.getOrDefault("is-default", false));
+
+            Object textTemplate = config.getOrDefault("text-template", "");
+            settings.setTextTemplate((String) textTemplate);
+
+            List<Map<String, Object>> webhooks = (List<Map<String, Object>>) config.get("webhooks");
+            if (webhooks != null) {
+                webhooks.forEach(webhook -> {
+                    Object secret = webhook.getOrDefault("secret", "");
+                    Object url = webhook.getOrDefault("url", "");
+                    settings.getWebhooks().add(new FeishuSettings.WebHookUrl((String) secret, (String) url));
                 });
             }
-            rules.setFeishus(feishuSettings);
-        }
+            rules.getFeishuSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read WeLink hook config into {@link WeLinkSettings}
      */
     @SuppressWarnings("unchecked")
-    private void readWeLinkConfig(Rules rules) {
-        Map<String, Object> welinkConfig = (Map<String, Object>) yamlData.getOrDefault(
-            "welinkHooks",
-            Collections.EMPTY_MAP
-        );
-        String textTemplate = (String) welinkConfig.get("textTemplate");
-        List<Map<String, String>> welinkWebHooks = (List<Map<String, String>>) welinkConfig.get("webhooks");
-        if (StringUtil.isBlank(textTemplate) || CollectionUtils.isEmpty(welinkWebHooks)) {
+    private void readWeLinkConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.welink.name());
+        if (configs == null) {
             return;
         }
-        List<WeLinkSettings.WebHookUrl> webHookUrls = welinkWebHooks.stream().map(
-            WeLinkSettings.WebHookUrl::generateFromMap
-        ).collect(Collectors.toList());
-
-        WeLinkSettings welinkSettings = new WeLinkSettings();
-        welinkSettings.setTextTemplate(textTemplate);
-        welinkSettings.setWebhooks(webHookUrls);
-        rules.setWelinks(welinkSettings);
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            String textTemplate = (String) config.get("text-template");
+            List<Map<String, String>> webhooks = (List<Map<String, String>>) config.get("webhooks");
+            if (StringUtil.isBlank(textTemplate) || CollectionUtils.isEmpty(webhooks)) {
+                return;
+            }
+            List<WeLinkSettings.WebHookUrl> webHookUrls = webhooks.stream().map(
+                WeLinkSettings.WebHookUrl::generateFromMap
+            ).collect(Collectors.toList());
+
+            WeLinkSettings settings = new WeLinkSettings(
+                k.toString(), AlarmHooksType.welink, (Boolean) config.getOrDefault("is-default", false));
+            settings.setTextTemplate(textTemplate);
+            settings.setWebhooks(webHookUrls);
+
+            rules.getWeLinkSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read PagerDuty hook config into {@link PagerDutySettings}
      */
-    private void readPagerDutyConfig(Rules rules) {
-        Map<String, Object> pagerDutyConfig = (Map<String, Object>) yamlData.get("pagerDutyHooks");
-        if (pagerDutyConfig != null) {
-            PagerDutySettings pagerDutySettings = new PagerDutySettings();
-            String textTemplate = (String) pagerDutyConfig.getOrDefault("textTemplate", "");
-            pagerDutySettings.setTextTemplate(textTemplate);
-
-            List<String> integrationKeys = (List<String>) pagerDutyConfig.get("integrationKeys");
+    @SuppressWarnings("unchecked")
+    private void readPagerDutyConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.pagerduty.name());
+        if (configs == null) {
+            return;
+        }
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            PagerDutySettings settings = new PagerDutySettings(
+                k.toString(), AlarmHooksType.pagerduty, (Boolean) config.getOrDefault("is-default", false));
+            Object textTemplate = config.getOrDefault("text-template", "");
+            settings.setTextTemplate((String) textTemplate);
+
+            List<String> integrationKeys = (List<String>) config.get("integration-keys");
             if (integrationKeys != null) {
-                pagerDutySettings.getIntegrationKeys().addAll(integrationKeys);
+                settings.getIntegrationKeys().addAll(integrationKeys);
             }
 
-            rules.setPagerDutySettings(pagerDutySettings);
-        }
+            rules.getPagerDutySettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
     }
 
     /**
      * Read Discord hook config into {@link DiscordSettings}
      */
     @SuppressWarnings("unchecked")
-    private void readDiscordConfig(Rules rules) {
-        Map<String, Object> discordConfig = (Map<String, Object>) yamlData.getOrDefault(
-                "discordHooks",
-                Collections.EMPTY_MAP
-        );
-        String textTemplate = (String) discordConfig.get("textTemplate");
-        List<Map<String, String>> discordWebHooks = (List<Map<String, String>>) discordConfig.get("webhooks");
-        if (StringUtil.isBlank(textTemplate) || CollectionUtils.isEmpty(discordWebHooks)) {
+    private void readDiscordConfig(Map hooks, Rules rules) {
+        Map configs = (Map) hooks.get(AlarmHooksType.discord.name());
+        if (configs == null) {
             return;
         }
-        List<DiscordSettings.WebHookUrl> webHookUrls = discordWebHooks.stream().map(
+        configs.forEach((k, v) -> {
+            Map<String, Object> config = (Map<String, Object>) v;
+            String textTemplate = (String) config.get("text-template");
+            List<Map<String, String>> webhooks = (List<Map<String, String>>) config.get("webhooks");
+            if (StringUtil.isBlank(textTemplate) || CollectionUtils.isEmpty(webhooks)) {
+                return;
+            }
+            List<DiscordSettings.WebHookUrl> webHookUrls = webhooks.stream().map(
                 DiscordSettings.WebHookUrl::generateFromMap
-        ).collect(Collectors.toList());
+            ).collect(Collectors.toList());
 
-        DiscordSettings discordSettings = new DiscordSettings();
-        discordSettings.setTextTemplate(textTemplate);
-        discordSettings.setWebhooks(webHookUrls);
-        rules.setDiscordSettings(discordSettings);
+            DiscordSettings settings = new DiscordSettings(
+                k.toString(), AlarmHooksType.discord, (Boolean) config.getOrDefault("is-default", false));
+            settings.setTextTemplate(textTemplate);
+            settings.setWebhooks(webHookUrls);
+
+            rules.getDiscordSettingsMap().put(settings.getFormattedName(), settings);
+            if (settings.isDefault()) {
+                this.defaultHooks.add(settings.getFormattedName());
+            }
+            this.allHooks.add(settings.getFormattedName());
+        });
+    }
+
+    private void checkSpecificHooks(String ruleName, Set<String> hooks) {
+        if (!this.allHooks.containsAll(hooks)) {
+            throw new IllegalArgumentException("rule: [" + ruleName + "] contains invalid hooks." +
+                                                   " Please check the hook is exist and name format is {hookType}.{hookName}");
+        }
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
index 2c4261e31e..a9bbdaabe1 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/RunningRule.java
@@ -25,6 +25,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.locks.ReentrantLock;
 import java.util.regex.Pattern;
@@ -76,6 +77,7 @@ public class RunningRule {
     private final AlarmMessageFormatter formatter;
     private final boolean onlyAsCondition;
     private final List<Tag> tags;
+    private final Set<String> hooks;
 
     public RunningRule(AlarmRule alarmRule) {
         metricsName = alarmRule.getMetricsName();
@@ -111,6 +113,7 @@ public class RunningRule {
                              .stream()
                              .map(e -> new Tag(e.getKey(), e.getValue()))
                              .collect(Collectors.toList());
+        this.hooks = alarmRule.getHooks();
     }
 
     /**
@@ -248,6 +251,7 @@ public class RunningRule {
                 alarmMessage.setStartTime(System.currentTimeMillis());
                 alarmMessage.setPeriod(this.period);
                 alarmMessage.setTags(this.tags);
+                alarmMessage.setHooks(this.hooks);
                 alarmMessageList.add(alarmMessage);
             }
         });
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallback.java
index 46a2ae7370..ab2c7efb32 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallback.java
@@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
 import org.apache.skywalking.oap.server.core.alarm.HttpAlarmCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
 import javax.crypto.Mac;
@@ -50,17 +51,27 @@ public class DingtalkHookCallback extends HttpAlarmCallback {
      */
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        if (alarmRulesWatcher.getDingtalkSettings() == null || alarmRulesWatcher.getDingtalkSettings().getWebhooks().isEmpty()) {
+        Map<String, DingtalkSettings> settingsMap = alarmRulesWatcher.getDingtalkSettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
-        final var dingtalkSettings = alarmRulesWatcher.getDingtalkSettings();
-        for (final var webHookUrl : dingtalkSettings.getWebhooks()) {
-            final var url = getUrl(webHookUrl);
-            for (final var alarmMessage : alarmMessages) {
-                final var requestBody = String.format(
-                        alarmRulesWatcher.getDingtalkSettings().getTextTemplate(), alarmMessage.getAlarmMessage()
-                );
-                post(URI.create(url), requestBody, Map.of());
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getWebhooks()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var webHookUrl : setting.getWebhooks()) {
+                final var url = getUrl(webHookUrl);
+                for (final var alarmMessage : messages) {
+                    final var requestBody = String.format(
+                        setting.getTextTemplate(), alarmMessage.getAlarmMessage()
+                    );
+                    post(URI.create(url), requestBody, Map.of());
+                }
             }
         }
     }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkSettings.java
index ba1d7501fe..475c44329c 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkSettings.java
@@ -19,27 +19,29 @@
 package org.apache.skywalking.oap.server.core.alarm.provider.dingtalk;
 
 import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
 
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class DingtalkSettings {
+public class DingtalkSettings extends AlarmHookSettings {
 
     private String textTemplate;
-    @Builder.Default
     private List<WebHookUrl> webhooks = new ArrayList<>();
 
+    public DingtalkSettings(final String name,
+                            final AlarmHooksType type,
+                            final boolean isDefault) {
+        super(name, type, isDefault);
+    }
+
     @AllArgsConstructor
     @Setter
     @Getter
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordHookCallback.java
index a0527690d0..44d53dcc6a 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordHookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordHookCallback.java
@@ -29,6 +29,7 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 
 /**
  * Use SkyWalking alarm Discord webhook API.
@@ -43,17 +44,28 @@ public class DiscordHookCallback extends HttpAlarmCallback {
      */
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        final var discordSettings = alarmRulesWatcher.getDiscordSettings();
-        if (discordSettings == null || discordSettings.getWebhooks().isEmpty()) {
+        Map<String, DiscordSettings> settingsMap = alarmRulesWatcher.getDiscordSettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
-        for (final var webHookUrl : discordSettings.getWebhooks()) {
-            for (final var alarmMessage : alarmMessages) {
-                final var content = String.format(
-                        discordSettings.getTextTemplate(),
+
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getWebhooks()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var webHookUrl : setting.getWebhooks()) {
+                for (final var alarmMessage : messages) {
+                    final var content = String.format(
+                        setting.getTextTemplate(),
                         alarmMessage.getAlarmMessage()
-                );
-                sendAlarmMessage(webHookUrl, content);
+                    );
+                    sendAlarmMessage(webHookUrl, content);
+                }
             }
         }
     }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordSettings.java
index 1c5b9e2b26..ddaea51704 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/discord/DiscordSettings.java
@@ -19,28 +19,30 @@
 package org.apache.skywalking.oap.server.core.alarm.provider.discord;
 
 import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
 
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class DiscordSettings {
+public class DiscordSettings extends AlarmHookSettings {
 
     private String textTemplate;
-    @Builder.Default
     private List<WebHookUrl> webhooks = new ArrayList<>();
 
+    public DiscordSettings(final String name,
+                           final AlarmHooksType type,
+                           final boolean isDefault) {
+        super(name, type, isDefault);
+    }
+
     @AllArgsConstructor
     @Setter
     @Getter
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java
index deb47cbf51..97eb6baa4d 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallback.java
@@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
 import org.apache.skywalking.oap.server.core.alarm.HttpAlarmCallback;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.apache.skywalking.oap.server.library.util.StringUtil;
 
 import javax.crypto.Mac;
@@ -52,14 +53,24 @@ public class FeishuHookCallback extends HttpAlarmCallback {
      */
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        if (alarmRulesWatcher.getFeishuSettings() == null || alarmRulesWatcher.getFeishuSettings().getWebhooks().isEmpty()) {
+        Map<String, FeishuSettings> settingsMap = alarmRulesWatcher.getFeishuSettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
-        final var feishuSettings = alarmRulesWatcher.getFeishuSettings();
-        for (final var webHookUrl : feishuSettings.getWebhooks()) {
-            for (final var alarmMessage : alarmMessages) {
-                final var requestBody = getRequestBody(webHookUrl, alarmMessage);
-                post(URI.create(webHookUrl.getUrl()), requestBody, Map.of());
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getWebhooks()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var webHookUrl : setting.getWebhooks()) {
+                for (final var alarmMessage : messages) {
+                    final var requestBody = getRequestBody(webHookUrl, alarmMessage, setting.getTextTemplate());
+                    post(URI.create(webHookUrl.getUrl()), requestBody, Map.of());
+                }
             }
         }
     }
@@ -67,9 +78,8 @@ public class FeishuHookCallback extends HttpAlarmCallback {
     /**
      * deal requestBody,if it has sign set the sign
      */
-    private String getRequestBody(FeishuSettings.WebHookUrl webHookUrl, AlarmMessage alarmMessage) {
-        final var requestBody = String.format(
-                alarmRulesWatcher.getFeishuSettings().getTextTemplate(), alarmMessage.getAlarmMessage()
+    private String getRequestBody(FeishuSettings.WebHookUrl webHookUrl, AlarmMessage alarmMessage, String textTemplate) {
+        final var requestBody = String.format(textTemplate, alarmMessage.getAlarmMessage()
         );
         final var gson = new Gson();
         final var jsonObject = gson.fromJson(requestBody, JsonObject.class);
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
index fa66b9560e..b4a3cef558 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
@@ -21,24 +21,28 @@ package org.apache.skywalking.oap.server.core.alarm.provider.feishu;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class FeishuSettings {
+public class FeishuSettings extends AlarmHookSettings {
 
     private String textTemplate;
     @Builder.Default
     private List<WebHookUrl> webhooks = new ArrayList<>();
 
+    public FeishuSettings(final String name,
+                          final AlarmHooksType type,
+                          final boolean isDefault) {
+        super(name, type, isDefault);
+    }
+
     @AllArgsConstructor
     @Setter
     @Getter
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCAlarmSetting.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCAlarmSetting.java
index 46975b6c0e..631d03269e 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCAlarmSetting.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCAlarmSetting.java
@@ -22,14 +22,22 @@ import java.util.Objects;
 import lombok.EqualsAndHashCode;
 import lombok.Getter;
 import lombok.Setter;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
 @Setter
 @Getter
-@EqualsAndHashCode
-public class GRPCAlarmSetting {
+@EqualsAndHashCode(callSuper = true)
+public class GRPCAlarmSetting extends AlarmHookSettings {
     private String targetHost;
     private int targetPort;
 
+    public GRPCAlarmSetting(final String name,
+                            final AlarmHooksType type,
+                            final boolean isDefault) {
+        super(name, type, isDefault);
+    }
+
     public boolean isEmptySetting() {
         return Objects.isNull(targetHost);
     }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCCallback.java
index 1eb3246441..2ae872f2f1 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPCCallback.java
@@ -19,7 +19,9 @@
 package org.apache.skywalking.oap.server.core.alarm.provider.grpc;
 
 import io.grpc.stub.StreamObserver;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.server.core.alarm.AlarmCallback;
@@ -30,6 +32,7 @@ import org.apache.skywalking.oap.server.core.alarm.grpc.KeyStringValuePair;
 import org.apache.skywalking.oap.server.core.alarm.grpc.Response;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.library.client.grpc.GRPCClient;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.apache.skywalking.oap.server.library.util.GRPCStreamStatus;
 
 /**
@@ -40,33 +43,52 @@ public class GRPCCallback implements AlarmCallback {
 
     private AlarmRulesWatcher alarmRulesWatcher;
 
-    private GRPCAlarmSetting alarmSetting;
+    private Map<String, GRPCAlarmSetting> alarmSettingMap;
 
-    private AlarmServiceGrpc.AlarmServiceStub alarmServiceStub;
+    private Map<String, AlarmServiceGrpc.AlarmServiceStub> alarmServiceStubMap;
 
-    private GRPCClient grpcClient;
+    private Map<String, GRPCClient> grpcClientMap;
 
     public GRPCCallback(AlarmRulesWatcher alarmRulesWatcher) {
         this.alarmRulesWatcher = alarmRulesWatcher;
-        alarmSetting = alarmRulesWatcher.getGrpchookSetting();
-
-        if (alarmSetting != null && !alarmSetting.isEmptySetting()) {
-            grpcClient = new GRPCClient(alarmSetting.getTargetHost(), alarmSetting.getTargetPort());
-            grpcClient.connect();
-            alarmServiceStub = AlarmServiceGrpc.newStub(grpcClient.getChannel());
+        this.alarmSettingMap = new HashMap<>();
+        this.alarmServiceStubMap = new HashMap<>();
+        this.grpcClientMap = new HashMap<>();
+        Map<String, GRPCAlarmSetting> alarmSettingMap = alarmRulesWatcher.getGrpchookSetting();
+        if (CollectionUtils.isNotEmpty(alarmSettingMap)) {
+            alarmSettingMap.forEach((name, alarmSetting) -> {
+                if (alarmSetting != null && !alarmSetting.isEmptySetting()) {
+                    GRPCClient grpcClient = new GRPCClient(alarmSetting.getTargetHost(), alarmSetting.getTargetPort());
+                    grpcClient.connect();
+                    grpcClientMap.put(name, grpcClient);
+                    alarmServiceStubMap.put(name, AlarmServiceGrpc.newStub(grpcClient.getChannel()));
+                }
+            });
         }
     }
 
     @Override
-    public void doAlarm(List<AlarmMessage> alarmMessage) {
-
+    public void doAlarm(List<AlarmMessage> alarmMessages) {
         // recreate gRPC client and stub if host and port configuration changed.
-        onGRPCAlarmSettingUpdated(alarmRulesWatcher.getGrpchookSetting());
+        Map<String, GRPCAlarmSetting> settinsMap = alarmRulesWatcher.getGrpchookSetting();
+        onGRPCAlarmSettingUpdated(settinsMap);
 
-        if (alarmSetting == null || alarmSetting.isEmptySetting() || alarmServiceStub == null) {
+        if (settinsMap == null || settinsMap.isEmpty()) {
             return;
         }
+        Map<String, List<AlarmMessage>> groupedMessages =  groupMessagesByHook(alarmMessages);
 
+        groupedMessages.forEach((hook, messages) -> {
+            if (alarmServiceStubMap.containsKey(hook)) {
+                sendAlarmMessages(alarmServiceStubMap.get(hook), messages, settinsMap.get(hook));
+            }
+        });
+
+    }
+
+    private void sendAlarmMessages(AlarmServiceGrpc.AlarmServiceStub alarmServiceStub,
+                                   List<AlarmMessage> alarmMessages,
+                                   GRPCAlarmSetting alarmSetting) {
         GRPCStreamStatus status = new GRPCStreamStatus();
 
         StreamObserver<org.apache.skywalking.oap.server.core.alarm.grpc.AlarmMessage> streamObserver =
@@ -93,7 +115,7 @@ public class GRPCCallback implements AlarmCallback {
                 }
             });
 
-        alarmMessage.forEach(message -> {
+        alarmMessages.forEach(message -> {
             org.apache.skywalking.oap.server.core.alarm.grpc.AlarmMessage.Builder builder =
                 org.apache.skywalking.oap.server.core.alarm.grpc.AlarmMessage.newBuilder();
 
@@ -125,41 +147,51 @@ public class GRPCCallback implements AlarmCallback {
             }
 
             if (log.isDebugEnabled()) {
-                log.debug("Send {} alarm message to {}:{}.", alarmMessage.size(),
+                log.debug("Send {} alarm message to {}:{}.", alarmMessages.size(),
                           alarmSetting.getTargetHost(), alarmSetting.getTargetPort()
                 );
             }
 
             if (sleepTime > 2000L) {
-                log.warn("Send {} alarm message to {}:{}, wait {} milliseconds.", alarmMessage.size(),
+                log.warn("Send {} alarm message to {}:{}, wait {} milliseconds.", alarmMessages.size(),
                          alarmSetting.getTargetHost(), alarmSetting.getTargetPort(), sleepTime
                 );
                 cycle = 2000L;
             }
         }
-    }
+}
 
-    private void onGRPCAlarmSettingUpdated(GRPCAlarmSetting grpcAlarmSetting) {
-        if (grpcAlarmSetting == null) {
-            if (grpcClient != null) {
-                grpcClient.shutdown();
-                log.debug("gRPC alarm hook target is empty, shutdown the old gRPC client.");
+    private void onGRPCAlarmSettingUpdated(Map<String, GRPCAlarmSetting> newAlarmSettingMap) {
+        if (newAlarmSettingMap == null || newAlarmSettingMap.isEmpty()) {
+            if (grpcClientMap != null) {
+                grpcClientMap.forEach((name, grpcClient) -> {
+                    grpcClient.shutdown();
+                    log.debug("gRPC alarm hook target is empty, shutdown the old gRPC client.");
+                });
             }
-            alarmServiceStub = null;
-            alarmSetting = null;
+            alarmServiceStubMap = null;
+            alarmSettingMap = null;
 
             return;
         }
 
-        if (!grpcAlarmSetting.equals(alarmSetting)) {
-            alarmSetting = grpcAlarmSetting;
-            if (grpcClient != null) {
-                grpcClient.shutdown();
-                log.debug("gRPC alarm hook target is changed, shutdown the old gRPC client.");
+        newAlarmSettingMap.forEach((name, newAlarmSetting) -> {
+            if (!newAlarmSetting.equals(alarmSettingMap.get(name))) {
+                GRPCClient grpcClient = grpcClientMap.get(name);
+                if (grpcClient != null) {
+                    grpcClient.shutdown();
+                    grpcClientMap.remove(name);
+                    alarmServiceStubMap.remove(name);
+                    log.debug("gRPC alarm hook target is changed, shutdown the old gRPC client.");
+                }
+                if (newAlarmSetting.isEmptySetting()) {
+                    return;
+                }
+                grpcClient = new GRPCClient(newAlarmSetting.getTargetHost(), newAlarmSetting.getTargetPort());
+                grpcClient.connect();
+                grpcClientMap.put(name, grpcClient);
+                alarmServiceStubMap.put(name, AlarmServiceGrpc.newStub(grpcClient.getChannel()));
             }
-            grpcClient = new GRPCClient(grpcAlarmSetting.getTargetHost(), grpcAlarmSetting.getTargetPort());
-            grpcClient.connect();
-            alarmServiceStub = AlarmServiceGrpc.newStub(grpcClient.getChannel());
-        }
+        });
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallback.java
index 40610e56bf..35ba9b11d2 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallback.java
@@ -31,32 +31,47 @@ import java.net.URI;
 import java.util.List;
 import java.util.Map;
 import java.util.UUID;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 
 @Slf4j
 @RequiredArgsConstructor
 public class PagerDutyHookCallback extends HttpAlarmCallback {
-    private static final String PAGER_DUTY_EVENTS_API_V2_URL = "https://events.pagerduty.com/v2/enqueue";
+    private static final String PAGER_DUTY_EVENTS_API_V2_URL = "http://events.pagerduty.com/v2/enqueue";
     private static final Gson GSON = new Gson();
 
     private final AlarmRulesWatcher alarmRulesWatcher;
 
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        if (alarmRulesWatcher.getPagerDutySettings() == null || alarmRulesWatcher.getPagerDutySettings().getIntegrationKeys().isEmpty()) {
+        Map<String, PagerDutySettings> settingsMap = alarmRulesWatcher.getPagerDutySettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
 
-        for (final var integrationKey : alarmRulesWatcher.getPagerDutySettings().getIntegrationKeys()) {
-            for (final var alarmMessage : alarmMessages) {
-                post(URI.create(PAGER_DUTY_EVENTS_API_V2_URL), getMessageBody(alarmMessage, integrationKey), Map.of());
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getIntegrationKeys()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var integrationKey : setting.getIntegrationKeys()) {
+                for (final var alarmMessage : messages) {
+                    post(
+                        URI.create(PAGER_DUTY_EVENTS_API_V2_URL),
+                        getMessageBody(alarmMessage, integrationKey, setting.getTextTemplate()), Map.of()
+                    );
+                }
             }
         }
     }
 
-    private String getMessageBody(AlarmMessage alarmMessage, String integrationKey) {
+    private String getMessageBody(AlarmMessage alarmMessage, String integrationKey, String textTemplate) {
         final var body = new JsonObject();
         final var payload = new JsonObject();
-        payload.add("summary", new JsonPrimitive(getFormattedMessage(alarmMessage)));
+        payload.add("summary", new JsonPrimitive(getFormattedMessage(alarmMessage, textTemplate)));
         payload.add("severity", new JsonPrimitive("warning"));
         payload.add("source", new JsonPrimitive("Skywalking"));
         body.add("payload", payload);
@@ -67,9 +82,8 @@ public class PagerDutyHookCallback extends HttpAlarmCallback {
         return GSON.toJson(body);
     }
 
-    private String getFormattedMessage(AlarmMessage alarmMessage) {
-        return String.format(
-                alarmRulesWatcher.getPagerDutySettings().getTextTemplate(), alarmMessage.getAlarmMessage()
+    private String getFormattedMessage(AlarmMessage alarmMessage, String textTemplate) {
+        return String.format(textTemplate, alarmMessage.getAlarmMessage()
         );
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutySettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutySettings.java
index 69f06c4167..d36f0577e2 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutySettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutySettings.java
@@ -18,26 +18,27 @@
 
 package org.apache.skywalking.oap.server.core.alarm.provider.pagerduty;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
 
 import java.util.ArrayList;
 import java.util.List;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class PagerDutySettings {
+public class PagerDutySettings extends AlarmHookSettings {
 
     private String textTemplate;
 
-    @Builder.Default
     private List<String> integrationKeys = new ArrayList<>();
+
+    public PagerDutySettings(final String name,
+                             final AlarmHooksType type,
+                             final boolean isDefault) {
+        super(name, type, isDefault);
+    }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackSettings.java
index 9c3df1cbbe..c87d60d378 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackSettings.java
@@ -20,23 +20,24 @@ package org.apache.skywalking.oap.server.core.alarm.provider.slack;
 
 import java.util.ArrayList;
 import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class SlackSettings {
+public class SlackSettings extends AlarmHookSettings {
 
     private String textTemplate;
 
-    @Builder.Default
     private List<String> webhooks = new ArrayList<>();
+
+    public SlackSettings(final String name,
+                         final AlarmHooksType type,
+                         final boolean isDefault) {
+        super(name, type, isDefault);
+    }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackhookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackhookCallback.java
index 9974880074..99ecdb6081 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackhookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackhookCallback.java
@@ -30,6 +30,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 
 /**
  * Use SkyWalking alarm slack webhook API calls a remote endpoints.
@@ -43,22 +44,34 @@ public class SlackhookCallback extends HttpAlarmCallback {
 
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        if (alarmRulesWatcher.getSlackSettings() == null || alarmRulesWatcher.getSlackSettings().getWebhooks().isEmpty()) {
+        Map<String, SlackSettings> settingsMap = alarmRulesWatcher.getSlackSettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
 
-        for (final var url : alarmRulesWatcher.getSlackSettings().getWebhooks()) {
-            final var jsonObject = new JsonObject();
-            final var jsonElements = new JsonArray();
-            for (AlarmMessage item : alarmMessages) {
-                jsonElements.add(GSON.fromJson(
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getWebhooks()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+
+            for (final var url : setting.getWebhooks()) {
+                final var jsonObject = new JsonObject();
+                final var jsonElements = new JsonArray();
+                for (AlarmMessage item : messages) {
+                    jsonElements.add(GSON.fromJson(
                         String.format(
-                                alarmRulesWatcher.getSlackSettings().getTextTemplate(), item.getAlarmMessage()
+                            setting.getTextTemplate(), item.getAlarmMessage()
                         ), JsonObject.class));
+                }
+                jsonObject.add("blocks", jsonElements);
+                final var body = GSON.toJson(jsonObject);
+                post(URI.create(url), body, Map.of());
             }
-            jsonObject.add("blocks", jsonElements);
-            final var body = GSON.toJson(jsonObject);
-            post(URI.create(url), body, Map.of());
         }
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/WebhookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookCallback.java
similarity index 56%
rename from oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/WebhookCallback.java
rename to oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookCallback.java
index 407b88c1e0..f4b022fb05 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/WebhookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookCallback.java
@@ -16,7 +16,7 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm.provider;
+package org.apache.skywalking.oap.server.core.alarm.provider.webhook;
 
 import com.google.gson.Gson;
 import lombok.RequiredArgsConstructor;
@@ -28,6 +28,8 @@ import java.io.IOException;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 
 /**
  * Use SkyWalking alarm webhook API calls a remote endpoints.
@@ -39,13 +41,24 @@ public class WebhookCallback extends HttpAlarmCallback {
     private final Gson gson = new Gson();
 
     @Override
-    public void doAlarm(List<AlarmMessage> alarmMessage) throws IOException, InterruptedException {
-        if (alarmRulesWatcher.getWebHooks().isEmpty()) {
+    public void doAlarm(List<AlarmMessage> alarmMessages) throws IOException, InterruptedException {
+        Map<String, WebhookSettings> settingsMap = alarmRulesWatcher.getWebHooks();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
 
-        for (String url : alarmRulesWatcher.getWebHooks()) {
-            post(URI.create(url), gson.toJson(alarmMessage), Map.of());
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getUrls()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var url : setting.getUrls()) {
+                post(URI.create(url), gson.toJson(alarmMessages), Map.of());
+            }
         }
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookSettings.java
similarity index 64%
copy from oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
copy to oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookSettings.java
index fa66b9560e..4daa76075f 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookSettings.java
@@ -16,35 +16,25 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm.provider.feishu;
+package org.apache.skywalking.oap.server.core.alarm.provider.webhook;
 
-import lombok.AllArgsConstructor;
-import lombok.Builder;
+import java.util.ArrayList;
+import java.util.List;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
-import java.util.ArrayList;
-import java.util.List;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class FeishuSettings {
-
-    private String textTemplate;
-    @Builder.Default
-    private List<WebHookUrl> webhooks = new ArrayList<>();
+public class WebhookSettings extends AlarmHookSettings {
+    private List<String> urls = new ArrayList<>();
 
-    @AllArgsConstructor
-    @Setter
-    @Getter
-    @ToString
-    public static class WebHookUrl {
-        private final String secret;
-        private final String url;
+    public WebhookSettings(final String name,
+                           final AlarmHooksType type,
+                           final boolean isDefault) {
+        super(name, type, isDefault);
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallback.java
index a5e1a716ae..42858ae52f 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallback.java
@@ -27,6 +27,7 @@ import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import java.net.URI;
 import java.util.List;
 import java.util.Map;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 
 /**
  * Use SkyWalking alarm wechat webhook API.
@@ -38,15 +39,27 @@ public class WechatHookCallback extends HttpAlarmCallback {
 
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        if (alarmRulesWatcher.getWechatSettings() == null || alarmRulesWatcher.getWechatSettings().getWebhooks().isEmpty()) {
+        Map<String, WechatSettings> settingsMap = alarmRulesWatcher.getWechatSettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
-        for (final var url : alarmRulesWatcher.getWechatSettings().getWebhooks()) {
-            for (final var alarmMessage : alarmMessages) {
-                final var requestBody = String.format(
-                        alarmRulesWatcher.getWechatSettings().getTextTemplate(), alarmMessage.getAlarmMessage()
-                );
-                post(URI.create(url), requestBody, Map.of());
+
+        Map<String, List<AlarmMessage>> groupedMessages = groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getWebhooks()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var url : setting.getWebhooks()) {
+                for (final var alarmMessage : messages) {
+                    final var requestBody = String.format(
+                        setting.getTextTemplate(), alarmMessage.getAlarmMessage()
+                    );
+                    post(URI.create(url), requestBody, Map.of());
+                }
             }
         }
     }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatSettings.java
index 03063534be..f29b897d41 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatSettings.java
@@ -20,23 +20,24 @@ package org.apache.skywalking.oap.server.core.alarm.provider.wechat;
 
 import java.util.ArrayList;
 import java.util.List;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class WechatSettings {
+public class WechatSettings extends AlarmHookSettings {
 
     private String textTemplate;
 
-    @Builder.Default
     private List<String> webhooks = new ArrayList<>();
+
+    public WechatSettings(final String name,
+                          final AlarmHooksType type,
+                          final boolean isDefault) {
+        super(name, type, isDefault);
+    }
 }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java
index 0d7dc51ff8..5505a05b95 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallback.java
@@ -22,6 +22,7 @@ import com.google.gson.Gson;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
+import java.util.Map;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
@@ -36,6 +37,7 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Optional;
 import java.util.UUID;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 
 /**
  * Use SkyWalking alarm WeLink webhook API.
@@ -50,19 +52,28 @@ public class WeLinkHookCallback extends HttpAlarmCallback {
      */
     @Override
     public void doAlarm(List<AlarmMessage> alarmMessages) throws Exception {
-        if (alarmRulesWatcher.getWeLinkSettings() == null
-                || alarmRulesWatcher.getWeLinkSettings().getWebhooks().isEmpty()) {
+        Map<String, WeLinkSettings> settingsMap = alarmRulesWatcher.getWeLinkSettings();
+        if (settingsMap == null || settingsMap.isEmpty()) {
             return;
         }
-        final var welinkSettings = alarmRulesWatcher.getWeLinkSettings();
-        for (final var webHookUrl : welinkSettings.getWebhooks()) {
-            final var accessToken = getAccessToken(webHookUrl);
-            for (final var alarmMessage : alarmMessages) {
-                final var content = String.format(
-                    alarmRulesWatcher.getWeLinkSettings().getTextTemplate(),
-                    alarmMessage.getAlarmMessage()
-                );
-                sendAlarmMessage(webHookUrl, accessToken, content);
+        Map<String, List<AlarmMessage>> groupedMessages =  groupMessagesByHook(alarmMessages);
+        for (Map.Entry<String, List<AlarmMessage>> entry : groupedMessages.entrySet()) {
+            var hookName = entry.getKey();
+            var messages = entry.getValue();
+            var setting = settingsMap.get(hookName);
+            if (setting == null || CollectionUtils.isEmpty(setting.getWebhooks()) || CollectionUtils.isEmpty(
+                messages)) {
+                continue;
+            }
+            for (final var webHookUrl : setting.getWebhooks()) {
+                final var accessToken = getAccessToken(webHookUrl);
+                for (final var alarmMessage : messages) {
+                    final var content = String.format(
+                        setting.getTextTemplate(),
+                        alarmMessage.getAlarmMessage()
+                    );
+                    sendAlarmMessage(webHookUrl, accessToken, content);
+                }
             }
         }
     }
diff --git a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java
index 4c7f2b8d14..5c46093199 100644
--- a/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java
+++ b/oap-server/server-alarm-plugin/src/main/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkSettings.java
@@ -22,24 +22,26 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 import lombok.AllArgsConstructor;
-import lombok.Builder;
 import lombok.Getter;
-import lombok.NoArgsConstructor;
 import lombok.Setter;
 import lombok.ToString;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHookSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 @Setter
 @Getter
 @ToString
-public class WeLinkSettings {
+public class WeLinkSettings extends AlarmHookSettings {
 
     private String textTemplate;
-    @Builder.Default
     private List<WebHookUrl> webhooks = new ArrayList<>();
 
+    public WeLinkSettings(final String name,
+                          final AlarmHooksType type,
+                          final boolean isDefault) {
+        super(name, type, isDefault);
+    }
+
     @AllArgsConstructor
     @Setter
     @Getter
@@ -59,12 +61,12 @@ public class WeLinkSettings {
         private final String groupIds;
 
         public static WebHookUrl generateFromMap(Map<String, String> params) {
-            String clientId = params.get("client_id");
-            String clientSecret = params.get("client_secret");
-            String accessTokenUrl = params.get("access_token_url");
-            String messageUrl = params.get("message_url");
-            String groupIds = params.get("group_ids");
-            String robotName = params.getOrDefault("robot_name", "robot");
+            String clientId = params.get("client-id");
+            String clientSecret = params.get("client-secret");
+            String accessTokenUrl = params.get("access-token-url");
+            String messageUrl = params.get("message-url");
+            String groupIds = params.get("group-ids");
+            String robotName = params.getOrDefault("robot-name", "robot");
             return new WebHookUrl(clientId, clientSecret, accessTokenUrl, messageUrl,
                                   robotName, groupIds
             );
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmCoreTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmCoreTest.java
index 27d95863e7..69dce8cbe6 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmCoreTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmCoreTest.java
@@ -49,7 +49,6 @@ public class AlarmCoreTest {
 
         Rules emptyRules = new Rules();
         emptyRules.setRules(new ArrayList<>(0));
-        emptyRules.setWebhooks(new ArrayList<>(0));
         AlarmCore core = new AlarmCore(new AlarmRulesWatcher(emptyRules, null));
 
         Map<String, List<RunningRule>> runningContext = Whitebox.getInternalState(core, "runningContext");
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java
index 9f2914e30c..bc0acc0d0b 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/AlarmRulesWatcherTest.java
@@ -19,6 +19,7 @@
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
 import org.apache.skywalking.oap.server.configuration.api.ConfigChangeWatcher;
+import org.apache.skywalking.oap.server.library.util.CollectionUtils;
 import org.apache.skywalking.oap.server.library.util.ResourceUtils;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
@@ -83,13 +84,13 @@ public class AlarmRulesWatcherTest {
         alarmRulesWatcher.notify(new ConfigChangeWatcher.ConfigChangeEvent(new String(chars, 0, length), ConfigChangeWatcher.EventType.MODIFY));
 
         assertEquals(3, alarmRulesWatcher.getRules().size());
-        assertEquals(2, alarmRulesWatcher.getWebHooks().size());
+        assertEquals(2, alarmRulesWatcher.getWebHooks().get(AlarmHooksType.webhook.name() + ".default").getUrls().size());
         assertNotNull(alarmRulesWatcher.getGrpchookSetting());
-        assertEquals(9888, alarmRulesWatcher.getGrpchookSetting().getTargetPort());
+        assertEquals(9888, alarmRulesWatcher.getGrpchookSetting().get(AlarmHooksType.gRPC.name() + ".default").getTargetPort());
         assertEquals(2, alarmRulesWatcher.getRunningContext().size());
         assertNotNull(alarmRulesWatcher.getDingtalkSettings());
         assertNotNull(alarmRulesWatcher.getWechatSettings());
-        assertNotNull(alarmRulesWatcher.getSlackSettings());
+        assertEquals(2, alarmRulesWatcher.getSlackSettings().size());
         assertNotNull(alarmRulesWatcher.getWeLinkSettings());
     }
 
@@ -104,7 +105,7 @@ public class AlarmRulesWatcherTest {
 
         assertEquals(0, alarmRulesWatcher.getRules().size());
         assertEquals(0, alarmRulesWatcher.getWebHooks().size());
-        assertNull(alarmRulesWatcher.getGrpchookSetting());
+        assertTrue(CollectionUtils.isEmpty(alarmRulesWatcher.getGrpchookSetting()));
         assertEquals(0, alarmRulesWatcher.getRunningContext().size());
     }
 
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluatorTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluatorTest.java
index 27fcf1e65a..9ccddc0747 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluatorTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/CompositeRuleEvaluatorTest.java
@@ -18,6 +18,7 @@
 
 package org.apache.skywalking.oap.server.core.alarm.provider;
 
+import java.util.HashSet;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
 import org.apache.skywalking.oap.server.core.alarm.provider.expression.Expression;
 import org.apache.skywalking.oap.server.core.alarm.provider.expression.ExpressionContext;
@@ -45,7 +46,7 @@ public class CompositeRuleEvaluatorTest {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
         CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule && b_rule", "composite rule {name},{id} triggered!", new HashMap<String, String>() {{
             put("key", "value");
-        }});
+        }}, new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         List<AlarmMessage> compositeMsgs = ruleEvaluate.evaluate(compositeAlarmRules, alarmMessages);
@@ -64,7 +65,7 @@ public class CompositeRuleEvaluatorTest {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
         CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule && b_rule", "composite rule {name} triggered!", new HashMap<String, String>() {{
             put("key", "value");
-        }});
+        }}, new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         List<AlarmMessage> compositeMsgs = ruleEvaluate.evaluate(compositeAlarmRules, alarmMessages);
@@ -81,7 +82,7 @@ public class CompositeRuleEvaluatorTest {
     @Test
     public void testEvaluateMessageWithNotExistsRule() {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
-        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule && not_exist_rule", "composite rule triggered!", new HashMap<>());
+        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule && not_exist_rule", "composite rule triggered!", new HashMap<>(), new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         List<AlarmMessage> compositeMsgs = ruleEvaluate.evaluate(compositeAlarmRules, alarmMessages);
@@ -91,7 +92,7 @@ public class CompositeRuleEvaluatorTest {
     @Test
     public void testEvaluateMessageWithException() {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
-        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule + b_rule", "composite rule triggered!", new HashMap<>());
+        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule + b_rule", "composite rule triggered!", new HashMap<>(), new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         List<AlarmMessage> compositeMsgs = ruleEvaluate.evaluate(compositeAlarmRules, alarmMessages);
@@ -135,7 +136,7 @@ public class CompositeRuleEvaluatorTest {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
         CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "a_rule || b_rule", "composite rule triggered!", new HashMap<String, String>() {{
             put("key", "value");
-        }});
+        }}, new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         alarmMessages.remove(0);
@@ -153,7 +154,7 @@ public class CompositeRuleEvaluatorTest {
     @Test
     public void testEvaluateMessageWithParenthesisAndOp() {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
-        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "(a_rule || b_rule) && c_rule", "composite rule triggered!", new HashMap<>());
+        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "(a_rule || b_rule) && c_rule", "composite rule triggered!", new HashMap<>(), new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         alarmMessages.remove(alarmMessages.size() - 1);
@@ -164,7 +165,7 @@ public class CompositeRuleEvaluatorTest {
     @Test
     public void testEvaluateMessageWithParenthesisAndOrOp() {
         List<CompositeAlarmRule> compositeAlarmRules = new ArrayList<>();
-        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "(a_rule && b_rule) || c_rule", "composite rule triggered!", new HashMap<>());
+        CompositeAlarmRule compositeAlarmRule = new CompositeAlarmRule("dummy", "(a_rule && b_rule) || c_rule", "composite rule triggered!", new HashMap<>(), new HashSet<>());
         compositeAlarmRules.add(compositeAlarmRule);
         List<AlarmMessage> alarmMessages = getAlarmMessages();
         List<AlarmMessage> compositeMsgs = ruleEvaluate.evaluate(compositeAlarmRules, alarmMessages);
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
index 28800ce5a4..2a757c5406 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/RulesReaderTest.java
@@ -23,7 +23,9 @@ import org.apache.skywalking.oap.server.core.alarm.provider.feishu.FeishuSetting
 import org.apache.skywalking.oap.server.core.alarm.provider.grpc.GRPCAlarmSetting;
 import org.apache.skywalking.oap.server.core.alarm.provider.pagerduty.PagerDutySettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.slack.SlackSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.webhook.WebhookSettings;
 import org.apache.skywalking.oap.server.core.alarm.provider.wechat.WechatSettings;
+import org.apache.skywalking.oap.server.core.alarm.provider.welink.WeLinkSettings;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 
@@ -54,32 +56,41 @@ public class RulesReaderTest {
         Assertions.assertEquals("service_c", ruleList.get(1).getExcludeNames().get(0));
         Assertions.assertEquals("Alarm caused by Rule service_percent_rule", ruleList.get(1).getMessage());
 
-        List<String> rulesWebhooks = rules.getWebhooks();
-        Assertions.assertEquals(2, rulesWebhooks.size());
-        Assertions.assertEquals("http://127.0.0.1/go-wechat/", rulesWebhooks.get(1));
+        //endpoint_percent_rule's hooks
+        Assertions.assertEquals(8, ruleList.get(0).getHooks().size());
+        //endpoint_percent_more_rule's hooks
+        Assertions.assertEquals(2, ruleList.get(2).getHooks().size());
 
-        GRPCAlarmSetting grpcAlarmSetting = rules.getGrpchookSetting();
+        WebhookSettings rulesWebhooks = rules.getWebhookSettingsMap().get(AlarmHooksType.webhook.name() + ".default");
+        Assertions.assertEquals(2, rulesWebhooks.getUrls().size());
+        Assertions.assertEquals("http://127.0.0.1/go-wechat/", rulesWebhooks.getUrls().get(1));
+
+        GRPCAlarmSetting grpcAlarmSetting = rules.getGrpcAlarmSettingMap().get(AlarmHooksType.gRPC.name() + ".default");
         assertNotNull(grpcAlarmSetting);
         assertThat(grpcAlarmSetting.getTargetHost()).isEqualTo("127.0.0.1");
         assertThat(grpcAlarmSetting.getTargetPort()).isEqualTo(9888);
 
-        SlackSettings slackSettings = rules.getSlacks();
+        SlackSettings slackSettings = rules.getSlackSettingsMap().get(AlarmHooksType.slack.name() + ".default");
         assertNotNull(slackSettings);
         assertThat(slackSettings.getWebhooks().size()).isEqualTo(1);
         assertThat(slackSettings.getWebhooks().get(0)).isEqualTo("https://hooks.slack.com/services/x/y/zssss");
         assertThat(slackSettings.getTextTemplate()).isInstanceOfAny(String.class);
 
-        WechatSettings wechatSettings = rules.getWecchats();
+        WechatSettings wechatSettings = rules.getWechatSettingsMap().get(AlarmHooksType.wechat.name() + ".default");
         assertNotNull(wechatSettings);
         assertThat(wechatSettings.getWebhooks().size()).isEqualTo(1);
         assertThat(wechatSettings.getWebhooks().get(0)).isEqualTo("https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key");
         assertThat(slackSettings.getTextTemplate()).isInstanceOfAny(String.class);
 
         List<CompositeAlarmRule> compositeRules = rules.getCompositeRules();
-        Assertions.assertEquals(1, compositeRules.size());
+        Assertions.assertEquals(2, compositeRules.size());
         Assertions.assertEquals("endpoint_percent_more_rule && endpoint_percent_rule", compositeRules.get(0).getExpression());
+        //comp1_rule's hooks
+        Assertions.assertEquals(8, compositeRules.get(0).getHooks().size());
+        //comp2_rule's hooks
+        Assertions.assertEquals(3, compositeRules.get(1).getHooks().size());
 
-        DingtalkSettings dingtalkSettings = rules.getDingtalks();
+        DingtalkSettings dingtalkSettings = rules.getDingtalkSettingsMap().get(AlarmHooksType.dingtalk.name() + ".default");
         assertThat(dingtalkSettings.getTextTemplate()).isInstanceOfAny(String.class);
         List<DingtalkSettings.WebHookUrl> webHookUrls = dingtalkSettings.getWebhooks();
         assertThat(webHookUrls.size()).isEqualTo(2);
@@ -88,7 +99,7 @@ public class RulesReaderTest {
         assertThat(webHookUrls.get(1).getUrl()).isEqualTo("https://oapi.dingtalk.com/robot/send?access_token=dummy_token2");
         assertNull(webHookUrls.get(1).getSecret());
 
-        FeishuSettings feishuSettings = rules.getFeishus();
+        FeishuSettings feishuSettings = rules.getFeishuSettingsMap().get(AlarmHooksType.feishu.name() + ".default");
         assertThat(feishuSettings.getTextTemplate()).isInstanceOfAny(String.class);
         List<FeishuSettings.WebHookUrl> feishuSettingsWebhooks = feishuSettings.getWebhooks();
         assertThat(feishuSettingsWebhooks.size()).isEqualTo(2);
@@ -97,11 +108,22 @@ public class RulesReaderTest {
         assertThat(feishuSettingsWebhooks.get(1).getUrl()).isEqualTo("https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token2");
         assertNull(feishuSettingsWebhooks.get(1).getSecret());
 
-        PagerDutySettings pagerDutySettings = rules.getPagerDutySettings();
+        PagerDutySettings pagerDutySettings = rules.getPagerDutySettingsMap().get(AlarmHooksType.pagerduty.name() + ".default");
         assertEquals("dummy_text_template", pagerDutySettings.getTextTemplate());
         List<String> pagerDutyIntegrationKeys = pagerDutySettings.getIntegrationKeys();
         assertEquals(2, pagerDutyIntegrationKeys.size());
         assertEquals("dummy_key", pagerDutyIntegrationKeys.get(0));
         assertEquals("dummy_key2", pagerDutyIntegrationKeys.get(1));
+
+        WeLinkSettings weLinkSettings = rules.getWeLinkSettingsMap().get(AlarmHooksType.welink.name() + ".default");
+        assertThat(weLinkSettings.getTextTemplate()).isInstanceOfAny(String.class);
+        List<WeLinkSettings.WebHookUrl> weiWebHookUrls = weLinkSettings.getWebhooks();
+        assertThat(weiWebHookUrls.size()).isEqualTo(1);
+        assertThat(weiWebHookUrls.get(0).getAccessTokenUrl()).isEqualTo("https://open.welink.huaweicloud.com/api/auth/v2/tickets");
+        assertThat(weiWebHookUrls.get(0).getMessageUrl()).isEqualTo("https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat");
+        assertThat(weiWebHookUrls.get(0).getClientId()).isEqualTo("dummy_client_id");
+        assertThat(weiWebHookUrls.get(0).getClientSecret()).isEqualTo("dummy_secret_key");
+        assertThat(weiWebHookUrls.get(0).getRobotName()).isEqualTo("robot");
+        assertThat(weiWebHookUrls.get(0).getGroupIds()).isEqualTo("dummy_group_id");
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallbackTest.java
index a15a0269c5..175d3349bb 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/dingtalk/DingtalkHookCallbackTest.java
@@ -25,6 +25,7 @@ import com.linecorp.armeria.common.HttpStatus;
 import com.linecorp.armeria.server.ServerBuilder;
 import com.linecorp.armeria.testing.junit5.server.ServerExtension;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
@@ -80,7 +81,14 @@ public class DingtalkHookCallbackTest {
         webHooks.add(new DingtalkSettings.WebHookUrl("", "http://127.0.0.1:" + SERVER.httpPort() + "/dingtalkhook/receiveAlarm?token=dummy_token"));
         Rules rules = new Rules();
         String template = "{\"msgtype\":\"text\",\"text\":{\"content\":\"Skywaling alarm: %s\"}}";
-        rules.setDingtalks(DingtalkSettings.builder().webhooks(webHooks).textTemplate(template).build());
+        DingtalkSettings setting1 = new DingtalkSettings("setting1", AlarmHooksType.dingtalk, true);
+        setting1.setWebhooks(webHooks);
+        setting1.setTextTemplate(template);
+        DingtalkSettings setting2 = new DingtalkSettings("setting2", AlarmHooksType.dingtalk, false);
+        setting2.setWebhooks(webHooks);
+        setting2.setTextTemplate(template);
+        rules.getDingtalkSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getDingtalkSettingsMap().put(setting2.getFormattedName(), setting2);
 
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         DingtalkHookCallback dingtalkCallBack = new DingtalkHookCallback(alarmRulesWatcher);
@@ -89,11 +97,13 @@ public class DingtalkHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         dingtalkCallBack.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
@@ -106,7 +116,14 @@ public class DingtalkHookCallbackTest {
         webHooks.add(new DingtalkSettings.WebHookUrl(secret, "http://127.0.0.1:" + SERVER.httpPort() + "/dingtalkhook/receiveAlarm?token=dummy_token"));
         Rules rules = new Rules();
         String template = "{\"msgtype\":\"text\",\"text\":{\"content\":\"Skywaling alarm: %s\"}}";
-        rules.setDingtalks(DingtalkSettings.builder().webhooks(webHooks).textTemplate(template).build());
+        DingtalkSettings setting1 = new DingtalkSettings("setting1", AlarmHooksType.dingtalk, true);
+        setting1.setWebhooks(webHooks);
+        setting1.setTextTemplate(template);
+        DingtalkSettings setting2 = new DingtalkSettings("setting2", AlarmHooksType.dingtalk, false);
+        setting2.setWebhooks(webHooks);
+        setting2.setTextTemplate(template);
+        rules.getDingtalkSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getDingtalkSettingsMap().put(setting2.getFormattedName(), setting2);
 
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         DingtalkHookCallback dingtalkCallBack = new DingtalkHookCallback(alarmRulesWatcher);
@@ -115,11 +132,13 @@ public class DingtalkHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         dingtalkCallBack.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java
index 36d5cfe8a9..371e4f06e1 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/feishu/FeishuHookCallbackTest.java
@@ -25,6 +25,7 @@ import com.linecorp.armeria.common.HttpStatus;
 import com.linecorp.armeria.server.ServerBuilder;
 import com.linecorp.armeria.testing.junit5.server.ServerExtension;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
@@ -78,7 +79,14 @@ public class FeishuHookCallbackTest {
         webHooks.add(new FeishuSettings.WebHookUrl("", "http://127.0.0.1:" + SERVER.httpPort() + "/feishuhook/receiveAlarm?token=dummy_token"));
         Rules rules = new Rules();
         String template = "{\"msg_type\":\"text\",\"content\":{\"text\":\"Skywaling alarm: %s\"}}";
-        rules.setFeishus(FeishuSettings.builder().webhooks(webHooks).textTemplate(template).build());
+        FeishuSettings setting1 = new FeishuSettings("setting1", AlarmHooksType.feishu, true);
+        setting1.setWebhooks(webHooks);
+        setting1.setTextTemplate(template);
+        FeishuSettings setting2 = new FeishuSettings("setting2", AlarmHooksType.feishu, false);
+        setting2.setWebhooks(webHooks);
+        setting2.setTextTemplate(template);
+        rules.getFeishuSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getFeishuSettingsMap().put(setting2.getFormattedName(), setting2);
 
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         FeishuHookCallback feishuHookCallback = new FeishuHookCallback(alarmRulesWatcher);
@@ -87,11 +95,13 @@ public class FeishuHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         feishuHookCallback.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
@@ -104,7 +114,14 @@ public class FeishuHookCallbackTest {
         webHooks.add(new FeishuSettings.WebHookUrl(secret, "http://127.0.0.1:" + SERVER.httpPort() + "/feishuhook/receiveAlarm?token=dummy_token"));
         Rules rules = new Rules();
         String template = "{\"msg_type\":\"text\",\"content\":{\"text\":\"Skywaling alarm: %s\"}}";
-        rules.setFeishus(FeishuSettings.builder().webhooks(webHooks).textTemplate(template).build());
+        FeishuSettings setting1 = new FeishuSettings("setting1", AlarmHooksType.feishu, true);
+        setting1.setWebhooks(webHooks);
+        setting1.setTextTemplate(template);
+        FeishuSettings setting2 = new FeishuSettings("setting2", AlarmHooksType.feishu, false);
+        setting2.setWebhooks(webHooks);
+        setting2.setTextTemplate(template);
+        rules.getFeishuSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getFeishuSettingsMap().put(setting2.getFormattedName(), setting2);
 
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         FeishuHookCallback feishuHookCallback = new FeishuHookCallback(alarmRulesWatcher);
@@ -113,11 +130,13 @@ public class FeishuHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         feishuHookCallback.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
@@ -130,7 +149,14 @@ public class FeishuHookCallbackTest {
         webHooks.add(new FeishuSettings.WebHookUrl(secret, "http://127.0.0.1:" + SERVER.httpPort() + "/feishuhook/receiveAlarm?token=dummy_token"));
         Rules rules = new Rules();
         String template = "{\"msg_type\":\"text\",\"content\":{\"text\":\"Skywaling alarm: %s\"},\"ats\":\"123\"}";
-        rules.setFeishus(FeishuSettings.builder().webhooks(webHooks).textTemplate(template).build());
+        FeishuSettings setting1 = new FeishuSettings("setting1", AlarmHooksType.feishu, true);
+        setting1.setWebhooks(webHooks);
+        setting1.setTextTemplate(template);
+        FeishuSettings setting2 = new FeishuSettings("setting2", AlarmHooksType.feishu, false);
+        setting2.setWebhooks(webHooks);
+        setting2.setTextTemplate(template);
+        rules.getFeishuSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getFeishuSettingsMap().put(setting2.getFormattedName(), setting2);
 
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         FeishuHookCallback feishuHookCallback = new FeishuHookCallback(alarmRulesWatcher);
@@ -139,11 +165,13 @@ public class FeishuHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         feishuHookCallback.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPChookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPChookCallbackTest.java
index 9154ddd6f9..d4732e19da 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPChookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/grpc/GRPChookCallbackTest.java
@@ -23,6 +23,7 @@ import com.google.common.collect.Lists;
 import java.util.Arrays;
 import java.util.List;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
@@ -40,16 +41,19 @@ public class GRPChookCallbackTest {
 
     @BeforeEach
     public void init() throws Exception {
-        GRPCAlarmSetting setting = new GRPCAlarmSetting();
-        setting.setTargetHost("127.0.0.1");
-        setting.setTargetPort(9888);
-
         Rules rules = new Rules();
-        rules.setGrpchookSetting(setting);
+        GRPCAlarmSetting setting1 = new GRPCAlarmSetting("setting1", AlarmHooksType.gRPC, true);
+        setting1.setTargetHost("127.0.0.1");
+        setting1.setTargetPort(9888);
+        GRPCAlarmSetting setting2 = new GRPCAlarmSetting("setting2", AlarmHooksType.gRPC, false);
+        setting2.setTargetHost("127.0.0.1");
+        setting2.setTargetPort(9888);
+        rules.getGrpcAlarmSettingMap().put(setting1.getFormattedName(), setting1);
+        rules.getGrpcAlarmSettingMap().put(setting2.getFormattedName(), setting2);
 
         alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         grpcCallback = new GRPCCallback(alarmRulesWatcher);
-        mockAlarmMessage();
+        mockAlarmMessage(setting1.getFormattedName(), setting2.getFormattedName());
     }
 
     @Test
@@ -59,15 +63,17 @@ public class GRPChookCallbackTest {
 
     @Test
     public void testGauchoSettingClean() {
-        GRPCAlarmSetting grpcAlarmSetting = new GRPCAlarmSetting();
         Rules rules = new Rules();
-        rules.setGrpchookSetting(grpcAlarmSetting);
+        GRPCAlarmSetting setting1 = new GRPCAlarmSetting("setting1111111", AlarmHooksType.gRPC, true);
+        GRPCAlarmSetting setting2 = new GRPCAlarmSetting("setting2222222", AlarmHooksType.gRPC, true);
+        rules.getGrpcAlarmSettingMap().put(setting1.getFormattedName(), setting1);
+        rules.getGrpcAlarmSettingMap().put(setting2.getFormattedName(), setting2);
         alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         grpcCallback = new GRPCCallback(alarmRulesWatcher);
         grpcCallback.doAlarm(alarmMessageList);
     }
 
-    private void mockAlarmMessage() {
+    private void mockAlarmMessage(String hook1, String hook2) {
         AlarmMessage alarmMessage = new AlarmMessage();
         alarmMessage.setId0("1");
         alarmMessage.setId1("2");
@@ -77,6 +83,17 @@ public class GRPChookCallbackTest {
         alarmMessage.setRuleName("mock_rule");
         alarmMessage.setStartTime(System.currentTimeMillis());
         alarmMessage.setTags(Arrays.asList(new Tag("key", "value")));
-        alarmMessageList = Lists.newArrayList(alarmMessage);
+        alarmMessage.getHooks().add(hook1);
+        AlarmMessage alarmMessage2 = new AlarmMessage();
+        alarmMessage2.setId0("21");
+        alarmMessage2.setId1("22");
+        alarmMessage2.setScope(Scope.Service.name());
+        alarmMessage2.setName("mock alarm message2");
+        alarmMessage2.setAlarmMessage("message2");
+        alarmMessage2.setRuleName("mock_rule2");
+        alarmMessage2.setStartTime(System.currentTimeMillis());
+        alarmMessage2.setTags(Arrays.asList(new Tag("key2", "value2")));
+        alarmMessage2.getHooks().add(hook1);
+        alarmMessageList = Lists.newArrayList(alarmMessage, alarmMessage2);
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallbackTest.java
index 62beade7e1..08b9c7b31d 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/pagerduty/PagerDutyHookCallbackTest.java
@@ -18,7 +18,9 @@
 
 package org.apache.skywalking.oap.server.core.alarm.provider.pagerduty;
 
+import java.util.ArrayList;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
@@ -39,36 +41,33 @@ public class PagerDutyHookCallbackTest {
         );
 
         Rules rules = new Rules();
-        rules.setPagerDutySettings(
-                PagerDutySettings.builder()
-                        .integrationKeys(integrationKeys)
-                        .textTemplate("Apache SkyWalking Alarm: \n %s.")
-                        .build()
-        );
-
+        PagerDutySettings setting1 = new PagerDutySettings("setting1", AlarmHooksType.pagerduty, true);
+        setting1.setIntegrationKeys(integrationKeys);
+        setting1.setTextTemplate("Apache SkyWalking Alarm: \\n %s.");
+        PagerDutySettings setting2 = new PagerDutySettings("setting2", AlarmHooksType.pagerduty, false);
+        setting2.setIntegrationKeys(integrationKeys);
+        setting2.setTextTemplate("Apache SkyWalking Alarm: \\n %s.");
+        rules.getPagerDutySettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getPagerDutySettingsMap().put(setting2.getFormattedName(), setting2);
         PagerDutyHookCallback pagerDutyHookCallback = new PagerDutyHookCallback(
-                new AlarmRulesWatcher(rules, null)
+            new AlarmRulesWatcher(rules, null)
         );
-
-        pagerDutyHookCallback.doAlarm(getMockAlarmMessages());
-
-        // please check your pagerduty account to see if the alarm is sent
-    }
-
-    private List<AlarmMessage> getMockAlarmMessages() {
+        List<AlarmMessage> alarmMessages = new ArrayList<>(2);
         AlarmMessage alarmMessage = new AlarmMessage();
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
-
+        alarmMessage.getHooks().add(setting1.getFormattedName());
+        alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
-        anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
+        anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
+        alarmMessages.add(anotherAlarmMessage);
 
-        return Arrays.asList(
-                alarmMessage,
-                anotherAlarmMessage
-        );
+        pagerDutyHookCallback.doAlarm(alarmMessages);
+
+        // please check your pagerduty account to see if the alarm is sent
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackHookCallbackTest.java
similarity index 71%
copy from oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java
copy to oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackHookCallbackTest.java
index 3a396e0cb6..33e9ef105a 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/slack/SlackHookCallbackTest.java
@@ -16,7 +16,7 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm.provider.wechat;
+package org.apache.skywalking.oap.server.core.alarm.provider.slack;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonObject;
@@ -24,7 +24,12 @@ import com.linecorp.armeria.common.HttpResponse;
 import com.linecorp.armeria.common.HttpStatus;
 import com.linecorp.armeria.server.ServerBuilder;
 import com.linecorp.armeria.testing.junit5.server.ServerExtension;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
@@ -32,12 +37,7 @@ import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.extension.RegisterExtension;
 
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-
-public class WechatHookCallbackTest {
+public class SlackHookCallbackTest {
     private static final AtomicBoolean IS_SUCCESS = new AtomicBoolean();
     private static final AtomicInteger COUNT = new AtomicInteger();
 
@@ -45,12 +45,12 @@ public class WechatHookCallbackTest {
     public static final ServerExtension SERVER = new ServerExtension() {
         @Override
         protected void configure(ServerBuilder sb) {
-            sb.service("/wechathook/receiveAlarm", (ctx, req) -> HttpResponse.from(
+            sb.service("/services/x/y/zssss", (ctx, req) -> HttpResponse.from(
                 req.aggregate().thenApply(r -> {
                     final String content = r.content().toStringUtf8();
-                    final JsonObject jsonObject = new Gson().fromJson(content, JsonObject.class);
-                    final String type = jsonObject.get("msgtype").getAsString();
-                    if (type.equalsIgnoreCase("text")) {
+                    final JsonObject jsonObject = new Gson().fromJson(content, JsonObject.class).get("blocks").getAsJsonArray().get(0).getAsJsonObject();
+                    final String type = jsonObject.get("type").getAsString();
+                    if (type.equalsIgnoreCase("section")) {
                         COUNT.incrementAndGet();
                         if (COUNT.get() == 2) {
                             IS_SUCCESS.set(true);
@@ -67,24 +67,33 @@ public class WechatHookCallbackTest {
     @Test
     public void testWechatWebhook() throws Exception {
         List<String> remoteEndpoints = new ArrayList<>();
-        remoteEndpoints.add("http://127.0.0.1:" + SERVER.httpPort() + "/wechathook/receiveAlarm");
+        remoteEndpoints.add("http://127.0.0.1:" + SERVER.httpPort() + "/services/x/y/zssss");
         Rules rules = new Rules();
-        String template = "{\"msgtype\":\"text\",\"text\":{\"content\":\"Skywaling alarm: %s\"}}";
-        rules.setWecchats(WechatSettings.builder().webhooks(remoteEndpoints).textTemplate(template).build());
+        String template = "{\"type\":\"section\",\"text\":{\"type\":\"mrkdwn\",\"text\":\":alarm_clock: *Apache Skywalking Alarm* \\n **%s**.\"}}";
+        SlackSettings setting1 = new SlackSettings("setting1", AlarmHooksType.slack, true);
+        setting1.setWebhooks(remoteEndpoints);
+        setting1.setTextTemplate(template);
+        SlackSettings setting2 = new SlackSettings("setting2", AlarmHooksType.slack, false);
+        setting2.setWebhooks(remoteEndpoints);
+        setting2.setTextTemplate(template);
+        rules.getSlackSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getSlackSettingsMap().put(setting2.getFormattedName(), setting2);
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
-        WechatHookCallback wechatHookCallback = new WechatHookCallback(alarmRulesWatcher);
+        SlackhookCallback slackhookCallback = new SlackhookCallback(alarmRulesWatcher);
         List<AlarmMessage> alarmMessages = new ArrayList<>(2);
         AlarmMessage alarmMessage = new AlarmMessage();
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
-        wechatHookCallback.doAlarm(alarmMessages);
+        slackhookCallback.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
     }
 }
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/WebhookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookCallbackTest.java
similarity index 80%
rename from oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/WebhookCallbackTest.java
rename to oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookCallbackTest.java
index 4e8e61b010..ddba59bf72 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/WebhookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/webhook/WebhookCallbackTest.java
@@ -16,7 +16,7 @@
  *
  */
 
-package org.apache.skywalking.oap.server.core.alarm.provider;
+package org.apache.skywalking.oap.server.core.alarm.provider.webhook;
 
 import com.google.gson.Gson;
 import com.google.gson.JsonArray;
@@ -25,6 +25,9 @@ import com.linecorp.armeria.common.HttpStatus;
 import com.linecorp.armeria.server.ServerBuilder;
 import com.linecorp.armeria.testing.junit5.server.ServerExtension;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
+import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
 import org.junit.jupiter.api.Assertions;
 import org.junit.jupiter.api.Test;
@@ -62,7 +65,12 @@ public class WebhookCallbackTest {
         List<String> remoteEndpoints = new ArrayList<>();
         remoteEndpoints.add("http://127.0.0.1:" + SERVER.httpPort() + "/webhook/receiveAlarm");
         Rules rules = new Rules();
-        rules.setWebhooks(remoteEndpoints);
+        WebhookSettings setting1 = new WebhookSettings("setting1", AlarmHooksType.wechat, true);
+        setting1.setUrls(remoteEndpoints);
+        WebhookSettings setting2 = new WebhookSettings("setting2", AlarmHooksType.wechat, false);
+        setting2.setUrls(remoteEndpoints);
+        rules.getWebhookSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getWebhookSettingsMap().put(setting2.getFormattedName(), setting2);
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         WebhookCallback webhookCallback = new WebhookCallback(alarmRulesWatcher);
         List<AlarmMessage> alarmMessages = new ArrayList<>(2);
@@ -70,11 +78,13 @@ public class WebhookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         webhookCallback.doAlarm(alarmMessages);
 
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java
index 3a396e0cb6..eb555022e4 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/wechat/WechatHookCallbackTest.java
@@ -25,6 +25,7 @@ import com.linecorp.armeria.common.HttpStatus;
 import com.linecorp.armeria.server.ServerBuilder;
 import com.linecorp.armeria.testing.junit5.server.ServerExtension;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
@@ -70,7 +71,14 @@ public class WechatHookCallbackTest {
         remoteEndpoints.add("http://127.0.0.1:" + SERVER.httpPort() + "/wechathook/receiveAlarm");
         Rules rules = new Rules();
         String template = "{\"msgtype\":\"text\",\"text\":{\"content\":\"Skywaling alarm: %s\"}}";
-        rules.setWecchats(WechatSettings.builder().webhooks(remoteEndpoints).textTemplate(template).build());
+        WechatSettings setting1 = new WechatSettings("setting1", AlarmHooksType.wechat, true);
+        setting1.setWebhooks(remoteEndpoints);
+        setting1.setTextTemplate(template);
+        WechatSettings setting2 = new WechatSettings("setting2", AlarmHooksType.wechat, false);
+        setting2.setWebhooks(remoteEndpoints);
+        setting2.setTextTemplate(template);
+        rules.getWechatSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getWechatSettingsMap().put(setting2.getFormattedName(), setting2);
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         WechatHookCallback wechatHookCallback = new WechatHookCallback(alarmRulesWatcher);
         List<AlarmMessage> alarmMessages = new ArrayList<>(2);
@@ -78,11 +86,13 @@ public class WechatHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         wechatHookCallback.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
diff --git a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java
index ea41298e3b..0b0921b94d 100644
--- a/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java
+++ b/oap-server/server-alarm-plugin/src/test/java/org/apache/skywalking/oap/server/core/alarm/provider/welink/WeLinkHookCallbackTest.java
@@ -19,6 +19,7 @@
 package org.apache.skywalking.oap.server.core.alarm.provider.welink;
 
 import com.google.gson.Gson;
+import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 import com.linecorp.armeria.common.HttpResponse;
 import com.linecorp.armeria.common.HttpStatus;
@@ -26,6 +27,7 @@ import com.linecorp.armeria.common.MediaType;
 import com.linecorp.armeria.server.ServerBuilder;
 import com.linecorp.armeria.testing.junit5.server.ServerExtension;
 import org.apache.skywalking.oap.server.core.alarm.AlarmMessage;
+import org.apache.skywalking.oap.server.core.alarm.provider.AlarmHooksType;
 import org.apache.skywalking.oap.server.core.alarm.provider.AlarmRulesWatcher;
 import org.apache.skywalking.oap.server.core.alarm.provider.Rules;
 import org.apache.skywalking.oap.server.core.source.DefaultScopeDefine;
@@ -51,18 +53,15 @@ public class WeLinkHookCallbackTest {
                     final String content = r.content().toStringUtf8();
                     final JsonObject jsonObject = new Gson().fromJson(content, JsonObject.class);
 
-                    if (COUNT.get() == 0) {
-                        String clientId = jsonObject.get("client_id").getAsString();
-                        if (clientId != null) {
-                            COUNT.incrementAndGet();
-                        }
-                    } else if (COUNT.get() >= 1) {
-                        String appMsgId = jsonObject.get("app_msg_id").getAsString();
-                        if (appMsgId != null) {
-                            COUNT.incrementAndGet();
-                        }
+                    JsonElement clientId = jsonObject.get("client_id");
+                    if (clientId != null) {
+                        COUNT.incrementAndGet();
                     }
-                    if (COUNT.get() == 2) {
+                    JsonElement appMsgId = jsonObject.get("app_msg_id");
+                    if (appMsgId != null) {
+                        COUNT.incrementAndGet();
+                    }
+                    if (COUNT.get() == 4) {
                         IS_SUCCESS.set(true);
                     }
 
@@ -81,7 +80,14 @@ public class WeLinkHookCallbackTest {
         ));
         Rules rules = new Rules();
         String template = "Apache SkyWalking Alarm: \n %s.";
-        rules.setWelinks(WeLinkSettings.builder().webhooks(webHooks).textTemplate(template).build());
+        WeLinkSettings setting1 = new WeLinkSettings("setting1", AlarmHooksType.welink, true);
+        setting1.setWebhooks(webHooks);
+        setting1.setTextTemplate(template);
+        WeLinkSettings setting2 = new WeLinkSettings("setting2", AlarmHooksType.welink, false);
+        setting2.setWebhooks(webHooks);
+        setting2.setTextTemplate(template);
+        rules.getWeLinkSettingsMap().put(setting1.getFormattedName(), setting1);
+        rules.getWeLinkSettingsMap().put(setting2.getFormattedName(), setting2);
 
         AlarmRulesWatcher alarmRulesWatcher = new AlarmRulesWatcher(rules, null);
         WeLinkHookCallback welinkHookCallback = new WeLinkHookCallback(alarmRulesWatcher);
@@ -90,11 +96,13 @@ public class WeLinkHookCallbackTest {
         alarmMessage.setScopeId(DefaultScopeDefine.SERVICE);
         alarmMessage.setRuleName("service_resp_time_rule");
         alarmMessage.setAlarmMessage("alarmMessage with [DefaultScopeDefine.All]");
+        alarmMessage.getHooks().add(setting1.getFormattedName());
         alarmMessages.add(alarmMessage);
         AlarmMessage anotherAlarmMessage = new AlarmMessage();
         anotherAlarmMessage.setRuleName("service_resp_time_rule_2");
         anotherAlarmMessage.setScopeId(DefaultScopeDefine.ENDPOINT);
         anotherAlarmMessage.setAlarmMessage("anotherAlarmMessage with [DefaultScopeDefine.Endpoint]");
+        anotherAlarmMessage.getHooks().add(setting2.getFormattedName());
         alarmMessages.add(anotherAlarmMessage);
         welinkHookCallback.doAlarm(alarmMessages);
         Assertions.assertTrue(IS_SUCCESS.get());
diff --git a/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml b/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
index cb60cb4465..48f9b60f9a 100755
--- a/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
+++ b/oap-server/server-alarm-plugin/src/test/resources/alarm-settings.yml
@@ -57,86 +57,135 @@ rules:
     # Control if this rule is just as a composite rule condition. do not notification alone
     only-as-condition: false
     message: Successful rate of endpoint {name} is higher than 60%
+    hooks:
+      - "slack.custom1"
+      - "pagerduty.custom1"
 
 composite-rules:
   comp1_rule:
     expression: endpoint_percent_more_rule && endpoint_percent_rule
     message: xxxxx
+  comp2_rule:
+    expression: endpoint_percent_more_rule && endpoint_percent_rule
+    message: xxxxx
+    hooks:
+      - "slack.default"
+      - "slack.custom1"
+      - "pagerduty.custom1"
 
-webhooks:
-  - http://127.0.0.1/notify/
-  - http://127.0.0.1/go-wechat/
+hooks:
+  webhook:
+    default:
+      is-default: true
+      urls:
+        - http://127.0.0.1/notify/
+        - http://127.0.0.1/go-wechat/
 
-gRPCHook:
-  target_host: 127.0.0.1
-  target_port: 9888
+  gRPC:
+    default:
+      is-default: true
+      target-host: 127.0.0.1
+      target-port: 9888
 
-slackHooks:
-  textTemplate: |-
-    {
-      "type": "section",
-      "text": {
-        "type": "mrkdwn",
-        "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
-      }
-    }
-  webhooks:
-    - https://hooks.slack.com/services/x/y/zssss
+  slack:
+    default:
+      is-default: true
+      text-template: |-
+        {
+          "type": "section",
+          "text": {
+            "type": "mrkdwn",
+            "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+          }
+        }
+      webhooks:
+        - https://hooks.slack.com/services/x/y/zssss
+    custom1:
+      text-template: |-
+        {
+          "type": "section",
+          "text": {
+            "type": "mrkdwn",
+            "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+          }
+        }
+      webhooks:
+        - https://hooks.slack.com/services/x/y/zssss
 
-wechatHooks:
-  textTemplate: |-
-    {
-      "msgtype": "text",
-      "text": {
-        "content": "Apache SkyWalking Alarm: \n %s."
-      }
-    }
-  webhooks:
-    - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key
+  wechat:
+    default:
+      is-default: true
+      text-template: |-
+        {
+          "msgtype": "text",
+          "text": {
+            "content": "Apache SkyWalking Alarm: \n %s."
+          }
+        }
+      webhooks:
+        - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key
 
-dingtalkHooks:
-  textTemplate: |-
-    {
-      "msgtype": "text",
-      "text": {
-        "content": "Apache SkyWalking Alarm: \n %s."
-      }
-    }
-  webhooks:
-    - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token
-      secret: dummysecret
-    - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token2
-      secret:
+  dingtalk:
+    default:
+      is-default: true
+      text-template: |-
+        {
+          "msgtype": "text",
+          "text": {
+            "content": "Apache SkyWalking Alarm: \n %s."
+          }
+        }
+      webhooks:
+        - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token
+          secret: dummysecret
+        - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token2
+          secret:
 
-feishuHooks:
-  textTemplate: |-
-    {
-      "msg_type": "text",
-      "content": {
-        "text": "Apache SkyWalking Alarm: \n %s."
-      }
-    }
-  webhooks:
-    - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
-      secret: dummysecret
-    - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token2
-      secret:
+  feishu:
+    default:
+      is-default: true
+      text-template: |-
+        {
+        "msg_type": "text",
+        # at someone with feishu_user_ids
+        # "ats": "feishu_user_id_1,feishu_user_id_2",
+        "content": {
+          "text": "Apache SkyWalking Alarm: \n %s."
+          }
+        }
+      webhooks:
+        - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
+          secret: dummysecret
+        - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token2
+          secret:
 
-welinkHooks:
-  textTemplate: "Apache SkyWalking Alarm: \n %s."
-  webhooks:
-    # you may find your own client_id and client_secret in your app, below are dummy, need to change.
-    - client_id: "dummy_client_id"
-      client_secret: dummy_secret_key
-      access_token_url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
-      message_url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
-      # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
-      group_ids: "dummy_group_id"
-      # make a name you like for the robot, it will display in group
-      robot_name: robot
+  welink:
+    default:
+      is-default: true
+      text-template: "Apache SkyWalking Alarm: \n %s."
+      webhooks:
+        # you may find your own client_id and client_secret in your app, below are dummy, need to change.
+        - client-id: "dummy_client_id"
+          client-secret: dummy_secret_key
+          access-token-url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
+          message-url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
+          # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
+          group-ids: "dummy_group_id"
+          # make a name you like for the robot, it will display in group
+          robot-name: robot
 
-pagerDutyHooks:
-  textTemplate: "dummy_text_template"
-  integrationKeys:
-    - dummy_key
-    - dummy_key2
\ No newline at end of file
+  pagerduty:
+    default:
+      is-default: true
+      text-template: "dummy_text_template"
+      integration-keys:
+        - dummy_key
+        - dummy_key2
+    custom1:
+      text-template: "Apache SkyWalking Alarm: \n %s."
+      integration-keys:
+        #    # you can find your integration key(s) on the Events API V2 integration page for your PagerDuty service(s).
+        #    # (you may need to create an Events API V2 integration for your PagerDuty service if you don't have one yet)
+        #    # below are dummy keys that should be replaced with your own integration keys.
+        - dummy_key
+        - dummy_key2
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java
index d4730876a0..b6e3a4d398 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmCallback.java
@@ -18,11 +18,27 @@
 
 package org.apache.skywalking.oap.server.core.alarm;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Set;
 
 /**
  * Alarm call back will be called by alarm implementor, after it decided alarm should be sent.
  */
 public interface AlarmCallback {
-    void doAlarm(List<AlarmMessage> alarmMessage) throws Exception;
+    default Map<String, List<AlarmMessage>> groupMessagesByHook(List<AlarmMessage> alarmMessages) {
+        Map<String, List<AlarmMessage>> result = new HashMap<>();
+        alarmMessages.forEach(message -> {
+            Set<String> hooks = message.getHooks();
+            hooks.forEach(hook -> {
+                List<AlarmMessage> messages = result.computeIfAbsent(hook, v -> new ArrayList<>());
+                messages.add(message);
+            });
+        });
+        return result;
+    }
+
+    void doAlarm(List<AlarmMessage> alarmMessages) throws Exception;
 }
diff --git a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmMessage.java b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmMessage.java
index c778c988ca..d3f629878f 100644
--- a/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmMessage.java
+++ b/oap-server/server-core/src/main/java/org/apache/skywalking/oap/server/core/alarm/AlarmMessage.java
@@ -18,6 +18,8 @@
 
 package org.apache.skywalking.oap.server.core.alarm;
 
+import java.util.HashSet;
+import java.util.Set;
 import lombok.Getter;
 import lombok.Setter;
 import org.apache.skywalking.oap.server.core.analysis.manual.searchtag.Tag;
@@ -41,4 +43,5 @@ public class AlarmMessage {
     private long startTime;
     private transient int period;
     private transient boolean onlyAsCondition;
+    private Set<String> hooks = new HashSet<>();
 }
diff --git a/oap-server/server-starter/src/main/resources/alarm-settings.yml b/oap-server/server-starter/src/main/resources/alarm-settings.yml
index e145d4e744..718376f08a 100755
--- a/oap-server/server-starter/src/main/resources/alarm-settings.yml
+++ b/oap-server/server-starter/src/main/resources/alarm-settings.yml
@@ -41,81 +41,133 @@ rules:
     tags:
       level: WARNING
 
-webhooks:
-#  - http://127.0.0.1/notify/
-#  - http://127.0.0.1/go-wechat/
-
-gRPCHook:
-#  target_host: 127.0.0.1
-#  target_port: 9888
-
-slackHooks:
-  textTemplate: |-
-    {
-      "type": "section",
-      "text": {
-        "type": "mrkdwn",
-        "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
-      }
-    }
-  webhooks:
-#    - https://hooks.slack.com/services/x/y/zssss
-
-wechatHooks:
-  textTemplate: |-
-    {
-      "msgtype": "text",
-      "text": {
-        "content": "Apache SkyWalking Alarm: \n %s."
-      }
-    }
-  webhooks:
-#    - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key
-
-dingtalkHooks:
-  textTemplate: |-
-    {
-      "msgtype": "text",
-      "text": {
-        "content": "Apache SkyWalking Alarm: \n %s."
-      }
-    }
-  webhooks:
-#    - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token
-#      secret: dummysecret
-
-feishuHooks:
-  textTemplate: |-
-    {
-    "msg_type": "text",
-    # at someone with feishu_user_ids
-    # "ats": "feishu_user_id_1,feishu_user_id_2",
-    "content": {
-      "text": "Apache SkyWalking Alarm: \n %s."
-      }
-    }
-  webhooks:
-#    - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
-#      secret: dummysecret
-
-welinkHooks:
-  textTemplate: "Apache SkyWalking Alarm: \n %s."
-  webhooks:
-#    # you may find your own client_id and client_secret in your app, below are dummy, need to change.
-#    - client_id: "dummy_client_id"
-#      client_secret: dummy_secret_key
-#      access_token_url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
-#      message_url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
-#      # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
-#      group_ids: "dummy_group_id"
-#      # make a name you like for the robot, it will display in group
-#      robot_name: robot
+  service_cpm_rule:
+    metrics-name: service_cpm
+    # [Optional] Default, match all services in this metrics
+    threshold: 1
+    op: ">"
+    period: 10
+    count: 1
+    tags:
+      level: WARNING
+    # Binding the specific names of the hooks when the alarm is triggered. The name format is {hookType}.{hookName}
+#    hooks:
+#      - "slack.custom1"
+#      - "pagerduty.custom1"
 
-pagerDutyHooks:
-  textTemplate: "Apache SkyWalking Alarm: \n %s."
-  integrationKeys:
-#    # you can find your integration key(s) on the Events API V2 integration page for your PagerDuty service(s).
-#    # (you may need to create an Events API V2 integration for your PagerDuty service if you don't have one yet)
-#    # below are dummy keys that should be replaced with your own integration keys.
-#    - dummy_key
-#    - dummy_key2
+hooks:
+#  webhook:
+#    default:
+#      # If true, this hook will apply on all rules, unless a rule has its own specific hook. Could have more than one default hooks in the same hook type.
+#      is-default: true
+#      urls:
+#        - http://127.0.0.1/notify/
+#        - http://127.0.0.1/go-wechat/
+#
+#  gRPC:
+#    default:
+#      is-default: true
+#      target-host: 127.0.0.1
+#      target-port: 9888
+#
+#  slack:
+#    default:
+#      is-default: true
+#      text-template: |-
+#        {
+#          "type": "section",
+#          "text": {
+#            "type": "mrkdwn",
+#            "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+#          }
+#        }
+#      webhooks:
+#        - https://hooks.slack.com/services/x/y/zssss
+#    custom1:
+#      text-template: |-
+#        {
+#          "type": "section",
+#          "text": {
+#            "type": "mrkdwn",
+#            "text": ":alarm_clock: *Apache Skywalking Alarm* \n **%s**."
+#          }
+#        }
+#      webhooks:
+#        - https://hooks.slack.com/services/x/y/custom1
+#
+#  pagerduty:
+#    default:
+#      is-default: true
+#      text-template: "Apache SkyWalking Alarm: \n %s."
+#      integration-keys:
+#    #    # you can find your integration key(s) on the Events API V2 integration page for your PagerDuty service(s).
+#    #    # (you may need to create an Events API V2 integration for your PagerDuty service if you don't have one yet)
+#    #    # below are dummy keys that should be replaced with your own integration keys.
+#        - dummy_key
+#        - dummy_key2
+#    custom1:
+#      text-template: "Apache SkyWalking Alarm: \n %s."
+#      integration-keys:
+#        #    # you can find your integration key(s) on the Events API V2 integration page for your PagerDuty service(s).
+#        #    # (you may need to create an Events API V2 integration for your PagerDuty service if you don't have one yet)
+#        #    # below are dummy keys that should be replaced with your own integration keys.
+#        - dummy_key
+#        - dummy_key2
+#
+#  wechat:
+#    default:
+#      is-default: true
+#      text-template: |-
+#        {
+#          "msgtype": "text",
+#          "text": {
+#            "content": "Apache SkyWalking Alarm: \n %s."
+#          }
+#        }
+#      webhooks:
+#        - https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=dummy_key
+#
+#  dingtalk:
+#    default:
+#      is-default: true
+#      text-template: |-
+#        {
+#          "msgtype": "text",
+#          "text": {
+#            "content": "Apache SkyWalking Alarm: \n %s."
+#          }
+#        }
+#      webhooks:
+#        - url: https://oapi.dingtalk.com/robot/send?access_token=dummy_token
+#          secret: dummysecret
+#
+#  feishu:
+#    default:
+#      is-default: true
+#      text-template: |-
+#        {
+#        "msg_type": "text",
+#        # at someone with feishu_user_ids
+#        # "ats": "feishu_user_id_1,feishu_user_id_2",
+#        "content": {
+#          "text": "Apache SkyWalking Alarm: \n %s."
+#          }
+#        }
+#      webhooks:
+#        - url: https://open.feishu.cn/open-apis/bot/v2/hook/dummy_token
+#          secret: dummysecret
+#
+#  welink:
+#    default:
+#      is-default: true
+#      text-template: "Apache SkyWalking Alarm: \n %s."
+#      webhooks:
+#        # you may find your own client_id and client_secret in your app, below are dummy, need to change.
+#        - client-id: "dummy_client_id"
+#          client-secret: dummy_secret_key
+#          access-token-url: https://open.welink.huaweicloud.com/api/auth/v2/tickets
+#          message-url: https://open.welink.huaweicloud.com/api/welinkim/v1/im-service/chat/group-chat
+#          # if you send to multi group at a time, separate group_ids with commas, e.g. "123xx","456xx"
+#          group-ids: "dummy_group_id"
+#          # make a name you like for the robot, it will display in group
+#          robot-name: robot
diff --git a/test/e2e-v2/cases/alarm/alarm-settings.yml b/test/e2e-v2/cases/alarm/alarm-settings.yml
index f765d408de..58843ac9d9 100755
--- a/test/e2e-v2/cases/alarm/alarm-settings.yml
+++ b/test/e2e-v2/cases/alarm/alarm-settings.yml
@@ -27,6 +27,8 @@ rules:
     tags:
       level: WARNING
       receivers: lisi
+    hooks:
+      - webhook.custom
   # service sla > 1%
   service_sla_rule:
     metrics-name: service_sla
@@ -45,5 +47,12 @@ composite-rules:
       level: CRITICAL
       receivers: zhangsan
 
-webhooks:
-  - http://provider:9090/alarm/receive
+hooks:
+  webhook:
+    default:
+      is-default: true
+      urls:
+        - http://provider:9090/alarm/receive
+    custom:
+      urls:
+        - http://provider:9090/alarm/receive