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

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


The following commit(s) were added to refs/heads/master by this push:
     new 3e7c2bc67f feat(alerter): support Alibaba Cloud Monitor webhook (#4296)
3e7c2bc67f is described below

commit 3e7c2bc67f28d9415a4574bc9918442ed368d5db
Author: lynx009 <[email protected]>
AuthorDate: Mon Aug 10 00:41:18 2026 +0800

    feat(alerter): support Alibaba Cloud Monitor webhook (#4296)
    
    Co-authored-by: Duansg <[email protected]>
---
 .../alert/dto/AlibabaCloudCmsExternAlert.java      | 142 +++++++++++++++
 .../impl/AlibabaCloudCmsExternAlertService.java    | 195 +++++++++++++++++++++
 .../AlibabaCloudCmsExternAlertServiceTest.java     | 180 +++++++++++++++++++
 .../alert-integration.component.ts                 |   5 +
 .../alert-integration/alibabacloud-cms.en-US.md    |  75 ++++++++
 .../alert-integration/alibabacloud-cms.zh-CN.md    |  75 ++++++++
 web-app/src/assets/i18n/en-US.json                 |   1 +
 web-app/src/assets/i18n/ja-JP.json                 |   1 +
 web-app/src/assets/i18n/ko-KR.json                 |   1 +
 web-app/src/assets/i18n/pt-BR.json                 |   1 +
 web-app/src/assets/i18n/zh-CN.json                 |   1 +
 web-app/src/assets/i18n/zh-TW.json                 |   1 +
 12 files changed, 678 insertions(+)

diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlibabaCloudCmsExternAlert.java
 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlibabaCloudCmsExternAlert.java
new file mode 100644
index 0000000000..2970dcd63a
--- /dev/null
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/dto/AlibabaCloudCmsExternAlert.java
@@ -0,0 +1,142 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.dto;
+
+import com.fasterxml.jackson.annotation.JsonAlias;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * Alibaba Cloud Monitor 2.0 webhook alert entity.
+ *
+ * @see <a 
href="https://help.aliyun.com/zh/cms/cloudmonitor-2-0/notification-object";>
+ *     Alibaba Cloud Monitor webhook payload fields</a>
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class AlibabaCloudCmsExternAlert {
+
+    private String specversion;
+
+    private String id;
+
+    private String type;
+
+    private String subtype;
+
+    private String source;
+
+    private String sourcetype;
+
+    private String time;
+
+    private Long timestamp;
+
+    private String subject;
+
+    private String datacontenttype;
+
+    private String severity;
+
+    private String status;
+
+    private String userId;
+
+    private String ruleId;
+
+    private String workspace;
+
+    private String traceId;
+
+    private String alertMessage;
+
+    private String alertEntityId;
+
+    private Resource resource;
+
+    private Map<String, Object> labels;
+
+    private Map<String, Object> annotations;
+
+    private AlertData data;
+
+    private Map<String, Object> alertEntityFields;
+
+    private String ruleUrl;
+
+    private String entityUrl;
+
+    private String alertRuleUrl;
+
+    private String alertHistoryUrl;
+
+    /**
+     * Alert resource.
+     */
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Resource {
+
+        private Entity entity;
+
+        private Map<String, Object> tags;
+    }
+
+    /**
+     * Alert resource entity.
+     */
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class Entity {
+
+        private String domain;
+
+        @JsonAlias("entity_type")
+        private String entityType;
+
+        @JsonAlias("entity_id")
+        private String entityId;
+
+        private Map<String, Object> prop;
+    }
+
+    /**
+     * Threshold alert data.
+     */
+    @Data
+    @Builder
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class AlertData {
+
+        private Object value;
+
+        private Object threshold;
+
+        private String comparisonOperator;
+    }
+}
diff --git 
a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaCloudCmsExternAlertService.java
 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaCloudCmsExternAlertService.java
new file mode 100644
index 0000000000..3b032e9fa0
--- /dev/null
+++ 
b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaCloudCmsExternAlertService.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service.impl;
+
+import java.time.Instant;
+import java.time.format.DateTimeParseException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.hertzbeat.alert.dto.AlibabaCloudCmsExternAlert;
+import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
+import org.apache.hertzbeat.alert.service.ExternAlertService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
+import org.apache.hertzbeat.common.util.JsonUtil;
+import org.springframework.stereotype.Service;
+
+/**
+ * Alibaba Cloud Monitor 2.0 external alert service.
+ */
+@Slf4j
+@Service
+public class AlibabaCloudCmsExternAlertService implements ExternAlertService {
+
+    private static final String SOURCE = "alibabacloud-cms";
+
+    private final AlarmCommonReduce alarmCommonReduce;
+
+    public AlibabaCloudCmsExternAlertService(AlarmCommonReduce 
alarmCommonReduce) {
+        this.alarmCommonReduce = alarmCommonReduce;
+    }
+
+    @Override
+    public void addExternAlert(String content) {
+        AlibabaCloudCmsExternAlert externAlert = JsonUtil.fromJson(content, 
AlibabaCloudCmsExternAlert.class);
+        if (externAlert == null || 
StringUtils.isBlank(externAlert.getStatus())) {
+            log.warn("Failed to parse Alibaba Cloud Monitor external alert 
content: {}", content);
+            return;
+        }
+        alarmCommonReduce.reduceAndSendAlarm(convert(externAlert));
+    }
+
+    @Override
+    public String supportSource() {
+        return SOURCE;
+    }
+
+    private SingleAlert convert(AlibabaCloudCmsExternAlert externAlert) {
+        boolean resolved = isResolved(externAlert);
+        long eventTime = getEventTime(externAlert);
+        return SingleAlert.builder()
+                .content(getAlertContent(externAlert))
+                .status(resolved ? CommonConstants.ALERT_STATUS_RESOLVED : 
CommonConstants.ALERT_STATUS_FIRING)
+                .startAt(eventTime)
+                .activeAt(resolved ? null : eventTime)
+                .endAt(resolved ? eventTime : null)
+                .labels(buildLabels(externAlert))
+                .annotations(buildAnnotations(externAlert))
+                .triggerTimes(1)
+                .build();
+    }
+
+    private boolean isResolved(AlibabaCloudCmsExternAlert externAlert) {
+        return "RESOLVED".equalsIgnoreCase(externAlert.getStatus())
+                || "RECOVERED".equalsIgnoreCase(externAlert.getStatus())
+                || "NORMAL_RESOLVE".equalsIgnoreCase(externAlert.getSubtype());
+    }
+
+    private long getEventTime(AlibabaCloudCmsExternAlert externAlert) {
+        if (externAlert.getTimestamp() != null && externAlert.getTimestamp() > 
0) {
+            return externAlert.getTimestamp();
+        }
+        if (StringUtils.isNotBlank(externAlert.getTime())) {
+            try {
+                return Instant.parse(externAlert.getTime()).toEpochMilli();
+            } catch (DateTimeParseException e) {
+                log.warn("Failed to parse Alibaba Cloud Monitor event time: 
{}", externAlert.getTime());
+            }
+        }
+        return Instant.now().toEpochMilli();
+    }
+
+    private Map<String, String> buildLabels(AlibabaCloudCmsExternAlert 
externAlert) {
+        Map<String, String> labels = new HashMap<>(16);
+        putValues(labels, externAlert.getLabels());
+        AlibabaCloudCmsExternAlert.Resource resource = 
externAlert.getResource();
+        if (resource != null) {
+            putValues(labels, resource.getTags());
+            AlibabaCloudCmsExternAlert.Entity entity = resource.getEntity();
+            if (entity != null) {
+                putIfNotBlank(labels, "resourceDomain", entity.getDomain());
+                putIfNotBlank(labels, "resourceType", entity.getEntityType());
+                putIfNotBlank(labels, "resourceId", entity.getEntityId());
+            }
+        }
+        labels.put("__source__", SOURCE);
+        putIfNotBlank(labels, CommonConstants.LABEL_ALERT_NAME, 
externAlert.getSubject());
+        putIfNotBlank(labels, CommonConstants.LABEL_ALERT_SEVERITY, 
convertSeverity(externAlert.getSeverity()));
+        putIfNotBlank(labels, "ruleId", externAlert.getRuleId());
+        putIfNotBlank(labels, "workspace", externAlert.getWorkspace());
+        putIfNotBlank(labels, "alertEntityId", externAlert.getAlertEntityId());
+        putIfNotBlank(labels, "userId", externAlert.getUserId());
+        return labels;
+    }
+
+    private Map<String, String> buildAnnotations(AlibabaCloudCmsExternAlert 
externAlert) {
+        Map<String, String> annotations = new HashMap<>(16);
+        putValues(annotations, externAlert.getAnnotations());
+        AlibabaCloudCmsExternAlert.Resource resource = 
externAlert.getResource();
+        if (resource != null && resource.getEntity() != null) {
+            putValues(annotations, resource.getEntity().getProp());
+        }
+        putValues(annotations, externAlert.getAlertEntityFields());
+        AlibabaCloudCmsExternAlert.AlertData data = externAlert.getData();
+        if (data != null) {
+            putValue(annotations, "value", data.getValue());
+            putValue(annotations, "threshold", data.getThreshold());
+            putIfNotBlank(annotations, "comparisonOperator", 
data.getComparisonOperator());
+        }
+        putIfNotBlank(annotations, "alertMessage", 
externAlert.getAlertMessage());
+        putIfNotBlank(annotations, "traceId", externAlert.getTraceId());
+        putIfNotBlank(annotations, "ruleUrl", externAlert.getRuleUrl());
+        putIfNotBlank(annotations, "entityUrl", externAlert.getEntityUrl());
+        putIfNotBlank(annotations, "alertRuleUrl", 
externAlert.getAlertRuleUrl());
+        putIfNotBlank(annotations, "alertHistoryUrl", 
externAlert.getAlertHistoryUrl());
+        return annotations;
+    }
+
+    private String getAlertContent(AlibabaCloudCmsExternAlert externAlert) {
+        if (StringUtils.isNotBlank(externAlert.getAlertMessage())) {
+            return externAlert.getAlertMessage();
+        }
+        if (StringUtils.isNotBlank(externAlert.getSubject())) {
+            return externAlert.getSubject();
+        }
+        return "Alibaba Cloud Monitor alert";
+    }
+
+    private String convertSeverity(String severity) {
+        if (StringUtils.isBlank(severity)) {
+            return null;
+        }
+        return switch (severity.toUpperCase(Locale.ROOT)) {
+            case "EMERGENCY" -> CommonConstants.ALERT_SEVERITY_EMERGENCY;
+            case "CRITICAL" -> CommonConstants.ALERT_SEVERITY_CRITICAL;
+            case "WARN", "WARNING" -> CommonConstants.ALERT_SEVERITY_WARNING;
+            case "INFO", "INFORMATIONAL" -> 
CommonConstants.ALERT_SEVERITY_INFO;
+            default -> severity.toLowerCase(Locale.ROOT);
+        };
+    }
+
+    private void putValues(Map<String, String> target, Map<String, Object> 
values) {
+        if (values == null || values.isEmpty()) {
+            return;
+        }
+        values.forEach((key, value) -> putValue(target, key, value));
+    }
+
+    private void putValue(Map<String, String> target, String key, Object 
value) {
+        if (StringUtils.isBlank(key) || value == null) {
+            return;
+        }
+        String stringValue;
+        if (value instanceof Map<?, ?> || value instanceof Collection<?>) {
+            stringValue = JsonUtil.toJson(value);
+        } else {
+            stringValue = String.valueOf(value);
+        }
+        putIfNotBlank(target, key, stringValue);
+    }
+
+    private void putIfNotBlank(Map<String, String> target, String key, String 
value) {
+        if (StringUtils.isNotBlank(value)) {
+            target.put(key, value);
+        }
+    }
+}
diff --git 
a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlibabaCloudCmsExternAlertServiceTest.java
 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlibabaCloudCmsExternAlertServiceTest.java
new file mode 100644
index 0000000000..53c89d113d
--- /dev/null
+++ 
b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/AlibabaCloudCmsExternAlertServiceTest.java
@@ -0,0 +1,180 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hertzbeat.alert.service;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.verify;
+import java.time.Instant;
+import org.apache.hertzbeat.alert.reduce.AlarmCommonReduce;
+import 
org.apache.hertzbeat.alert.service.impl.AlibabaCloudCmsExternAlertService;
+import org.apache.hertzbeat.common.constants.CommonConstants;
+import org.apache.hertzbeat.common.entity.alerter.SingleAlert;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+/**
+ * Unit test for {@link AlibabaCloudCmsExternAlertService}.
+ */
+@ExtendWith(MockitoExtension.class)
+class AlibabaCloudCmsExternAlertServiceTest {
+
+    private static final long EVENT_TIME = 1785300000123L;
+
+    @Mock
+    private AlarmCommonReduce alarmCommonReduce;
+
+    private AlibabaCloudCmsExternAlertService externAlertService;
+
+    @BeforeEach
+    void setUp() {
+        externAlertService = new 
AlibabaCloudCmsExternAlertService(alarmCommonReduce);
+    }
+
+    @Test
+    void shouldConvertTriggeredAlert() {
+        externAlertService.addExternAlert("""
+                {
+                  "specversion": "1.0",
+                  "id": "alert-event-1",
+                  "type": "ALERT",
+                  "subtype": "NORMAL_TRIGGER",
+                  "time": "2026-07-29T06:00:00Z",
+                  "timestamp": 1785300000123,
+                  "subject": "ECS CPU usage is high",
+                  "severity": "WARNING",
+                  "status": "OCCURRED",
+                  "userId": "123456",
+                  "ruleId": "rule-1",
+                  "workspace": "default-cms-123456-cn-hangzhou",
+                  "traceId": "trace-1",
+                  "alertMessage": "CPU usage exceeded 80%",
+                  "alertEntityId": "ecs:i-123",
+                  "resource": {
+                    "entity": {
+                      "domain": "ecs",
+                      "entity_type": "instance",
+                      "entity_id": "i-123",
+                      "prop": {
+                        "instanceName": "api-server"
+                      }
+                    },
+                    "tags": {
+                      "regionId": "cn-hangzhou",
+                      "environment": "production"
+                    }
+                  },
+                  "labels": {
+                    "_cms_region": "cn-hangzhou",
+                    "customNumber": 7
+                  },
+                  "annotations": {
+                    "current_value": "92.5"
+                  },
+                  "data": {
+                    "value": 92.5,
+                    "threshold": 80,
+                    "comparisonOperator": ">"
+                  },
+                  "alertEntityFields": {
+                    "privateIp": "10.0.0.1"
+                  },
+                  "alertHistoryUrl": 
"https://cmsnext.console.aliyun.com/history";,
+                  "futureField": "ignored"
+                }
+                """);
+
+        SingleAlert alert = captureAlert();
+        assertEquals(CommonConstants.ALERT_STATUS_FIRING, alert.getStatus());
+        assertEquals(EVENT_TIME, alert.getStartAt());
+        assertEquals(EVENT_TIME, alert.getActiveAt());
+        assertNull(alert.getEndAt());
+        assertEquals("CPU usage exceeded 80%", alert.getContent());
+        assertEquals("alibabacloud-cms", alert.getLabels().get("__source__"));
+        assertEquals("ECS CPU usage is high", 
alert.getLabels().get("alertname"));
+        assertEquals(CommonConstants.ALERT_SEVERITY_WARNING, 
alert.getLabels().get("severity"));
+        assertEquals("instance", alert.getLabels().get("resourceType"));
+        assertEquals("i-123", alert.getLabels().get("resourceId"));
+        assertEquals("7", alert.getLabels().get("customNumber"));
+        assertEquals("api-server", alert.getAnnotations().get("instanceName"));
+        assertEquals("92.5", alert.getAnnotations().get("value"));
+        assertEquals("80", alert.getAnnotations().get("threshold"));
+        assertEquals("10.0.0.1", alert.getAnnotations().get("privateIp"));
+    }
+
+    @Test
+    void shouldConvertResolvedAlertAndIsoTime() {
+        externAlertService.addExternAlert("""
+                {
+                  "subtype": "NORMAL_RESOLVE",
+                  "time": "2026-07-29T06:00:00Z",
+                  "subject": "ECS CPU usage is high",
+                  "severity": "CRITICAL",
+                  "status": "RESOLVED",
+                  "labels": {
+                    "instanceId": "i-123"
+                  }
+                }
+                """);
+
+        SingleAlert alert = captureAlert();
+        long expectedTime = 
Instant.parse("2026-07-29T06:00:00Z").toEpochMilli();
+        assertEquals(CommonConstants.ALERT_STATUS_RESOLVED, alert.getStatus());
+        assertEquals(expectedTime, alert.getStartAt());
+        assertNull(alert.getActiveAt());
+        assertEquals(expectedTime, alert.getEndAt());
+        assertEquals("ECS CPU usage is high", alert.getContent());
+        assertEquals(CommonConstants.ALERT_SEVERITY_CRITICAL, 
alert.getLabels().get("severity"));
+    }
+
+    @Test
+    void shouldTreatRecoveredStatusAsResolved() {
+        externAlertService.addExternAlert("""
+                {
+                  "timestamp": 1785300000123,
+                  "subject": "Recovered alert",
+                  "status": "RECOVERED"
+                }
+                """);
+
+        SingleAlert alert = captureAlert();
+        assertEquals(CommonConstants.ALERT_STATUS_RESOLVED, alert.getStatus());
+        assertEquals(EVENT_TIME, alert.getEndAt());
+    }
+
+    @Test
+    void shouldIgnoreInvalidPayload() {
+        externAlertService.addExternAlert("invalid json");
+        externAlertService.addExternAlert("{\"subject\":\"missing status\"}");
+
+        verify(alarmCommonReduce, 
never()).reduceAndSendAlarm(any(SingleAlert.class));
+        assertEquals("alibabacloud-cms", externAlertService.supportSource());
+    }
+
+    private SingleAlert captureAlert() {
+        ArgumentCaptor<SingleAlert> captor = 
ArgumentCaptor.forClass(SingleAlert.class);
+        verify(alarmCommonReduce).reduceAndSendAlarm(captor.capture());
+        return captor.getValue();
+    }
+}
diff --git 
a/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts 
b/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts
index 57eab71d8f..f4e539821d 100644
--- 
a/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts
+++ 
b/web-app/src/app/routes/alert/alert-integration/alert-integration.component.ts
@@ -80,6 +80,11 @@ export class AlertIntegrationComponent implements OnInit {
       name: this.i18nSvc.fanyi('alert.integration.source.tencent'),
       icon: 'assets/img/integration/tencent.svg'
     },
+    {
+      id: 'alibabacloud-cms',
+      name: this.i18nSvc.fanyi('alert.integration.source.alibabacloud-cms'),
+      icon: 'assets/img/integration/alibabacloud.svg'
+    },
     {
       id: 'alibabacloud-sls',
       name: this.i18nSvc.fanyi('alert.integration.source.alibabacloud-sls'),
diff --git a/web-app/src/assets/doc/alert-integration/alibabacloud-cms.en-US.md 
b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.en-US.md
new file mode 100644
index 0000000000..e36af233f7
--- /dev/null
+++ b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.en-US.md
@@ -0,0 +1,75 @@
+> Send Alibaba Cloud Monitor 2.0 alerts to the HertzBeat alert platform 
through a webhook.
+
+### Prepare a HertzBeat API token
+
+1. Click **Manage API Tokens** in the upper-right corner of this page.
+2. Create a token and save it securely. The complete token is displayed only 
once.
+
+### Create an Alibaba Cloud Monitor webhook
+
+1. Log on to the [Alibaba Cloud Monitor 2.0 
console](https://cmsnext.console.aliyun.com/).
+2. Select or create the target workspace, then go to **Alert Center** > 
**Notification Management** > **Notification Objects**.
+3. Open the **Custom Webhook** tab and click **Create Webhook**.
+4. Configure the webhook:
+   - Name: `HertzBeat`
+   - Identifier: for example, `hertzbeat`
+   - URL:
+
+     ```text
+     http://{hertzbeat_host}:1157/api/alerts/report/alibabacloud-cms
+     ```
+
+   - Headers: add `Authorization` with the value `Bearer {token}`
+   - Method: `POST`
+   - Data format: `JSON`
+   - Language: select as needed
+5. Save the webhook.
+
+> Use the notification objects in an Alibaba Cloud Monitor 2.0 workspace. 
Notification objects in Prometheus Monitoring or ARMS Alert Management use a 
different webhook format and are not supported by this integration.
+
+> `{hertzbeat_host}` must be publicly reachable from Alibaba Cloud Monitor. 
Expose this endpoint through an HTTPS reverse proxy in production.
+
+### Bind an alert rule
+
+1. Go to **Alert Center** > **Alert Management** > **Alert Rules**.
+2. Create or edit an alert rule.
+3. Select the HertzBeat custom webhook in the alert notification settings.
+4. Enable recovery notifications if alerts should be resolved automatically in 
HertzBeat.
+5. Save and enable the alert rule.
+
+### Field mapping
+
+| Alibaba Cloud Monitor field | HertzBeat field |
+| --- | --- |
+| `status: OCCURRED/PERSISTENT` | `status: firing` |
+| `status: RESOLVED/RECOVERED` | `status: resolved` |
+| `subject` | `labels.alertname` |
+| `severity` | `labels.severity` |
+| `alertMessage` | Alert content |
+| `labels`, `resource.tags` | Alert labels |
+| `annotations`, threshold, and resource properties | Alert annotations |
+| `timestamp` or `time` | Alert time |
+
+### Troubleshooting
+
+#### The webhook returns 401 or 403
+
+- Make sure the HertzBeat API token is active.
+- Make sure the header is named `Authorization` and its value starts with 
`Bearer `.
+
+#### HertzBeat does not receive an alert
+
+- Make sure the webhook URL and port are publicly reachable.
+- Make sure the data format is `JSON` and the request method is `POST`.
+- Check Alibaba Cloud Monitor alert history and the HertzBeat service logs.
+- If source IP allowlisting is enabled, use the latest CIDR list in the 
Alibaba Cloud documentation.
+
+#### An alert is not resolved automatically
+
+- Make sure recovery notifications are enabled in the alert rule or 
notification policy.
+- Make sure the recovery webhook contains a `status` of `RESOLVED` or 
`RECOVERED`.
+
+For more information:
+
+- [Alibaba Cloud Monitor notification objects and webhook 
fields](https://help.aliyun.com/en/cms/cloudmonitor-2-0/notification-object)
+- [Alibaba Cloud Monitor alert 
rules](https://help.aliyun.com/en/cms/cloudmonitor-2-0/alert-rules-cms-2-0)
diff --git a/web-app/src/assets/doc/alert-integration/alibabacloud-cms.zh-CN.md 
b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.zh-CN.md
new file mode 100644
index 0000000000..72ba07b4e3
--- /dev/null
+++ b/web-app/src/assets/doc/alert-integration/alibabacloud-cms.zh-CN.md
@@ -0,0 +1,75 @@
+> 将阿里云云监控 2.0 的告警通过 Webhook 发送到 HertzBeat 告警平台。
+
+### 准备 HertzBeat API Token
+
+1. 单击页面右上角的 **管理 API Token**。
+2. 创建一个 Token 并立即妥善保存。Token 只会完整显示一次。
+
+### 创建阿里云云监控 Webhook
+
+1. 登录 [阿里云云监控 2.0 控制台](https://cmsnext.console.aliyun.com/)。
+2. 选择或创建目标工作空间,然后进入 **告警中心** > **通知管理** > **通知对象**。
+3. 选择 **自定义 Webhook** 页签,单击 **新建 Webhook**。
+4. 填写 Webhook 配置:
+   - 名称:`HertzBeat`
+   - 标识符:例如 `hertzbeat`
+   - URL:
+
+     ```text
+     http://{hertzbeat_host}:1157/api/alerts/report/alibabacloud-cms
+     ```
+
+   - Headers:添加 `Authorization`,值为 `Bearer {token}`
+   - Method:`POST`
+   - 数据格式:`JSON`
+   - 语言:按需选择
+5. 保存 Webhook。
+
+> 请使用云监控 2.0 工作空间内的通知对象。Prometheus 监控或 ARMS 告警管理中的通知对象使用不同的 Webhook 格式,不适用于此集成。
+
+> `{hertzbeat_host}` 必须是阿里云云监控可以访问的公网地址。生产环境建议通过 HTTPS 反向代理暴露此接口。
+
+### 绑定告警规则
+
+1. 进入 **告警中心** > **告警管理** > **告警规则**。
+2. 创建或编辑告警规则。
+3. 在告警通知中选择上一步创建的 HertzBeat 自定义 Webhook。
+4. 如需在 HertzBeat 中自动恢复告警,请同时启用恢复通知。
+5. 保存并启用告警规则。
+
+### 字段映射
+
+| 阿里云云监控字段 | HertzBeat 字段 |
+| --- | --- |
+| `status: OCCURRED/PERSISTENT` | `status: firing` |
+| `status: RESOLVED/RECOVERED` | `status: resolved` |
+| `subject` | `labels.alertname` |
+| `severity` | `labels.severity` |
+| `alertMessage` | 告警内容 |
+| `labels`、`resource.tags` | 告警标签 |
+| `annotations`、阈值和资源属性 | 告警注解 |
+| `timestamp` 或 `time` | 告警时间 |
+
+### 常见问题
+
+#### 返回 401 或 403
+
+- 确认已创建有效的 HertzBeat API Token。
+- 确认 Webhook Header 名称为 `Authorization`,值以 `Bearer ` 开头。
+
+#### HertzBeat 未收到告警
+
+- 确认 Webhook URL 可从公网访问,并且端口已放行。
+- 确认数据格式选择为 `JSON`,请求方法选择为 `POST`。
+- 检查阿里云云监控的告警历史以及 HertzBeat 服务日志。
+- 若配置了来源 IP 白名单,请以阿里云官方文档中的最新地址段为准。
+
+#### 告警没有自动恢复
+
+- 确认告警规则或通知策略已启用恢复通知。
+- 确认恢复 Webhook 中的 `status` 为 `RESOLVED` 或 `RECOVERED`。
+
+更多信息请参考:
+
+- [阿里云云监控通知对象与 Webhook 
字段](https://help.aliyun.com/zh/cms/cloudmonitor-2-0/notification-object)
+- 
[阿里云云监控告警规则](https://help.aliyun.com/zh/cms/cloudmonitor-2-0/alert-rules-cms-2-0)
diff --git a/web-app/src/assets/i18n/en-US.json 
b/web-app/src/assets/i18n/en-US.json
index f2eaf8c63f..167866115d 100644
--- a/web-app/src/assets/i18n/en-US.json
+++ b/web-app/src/assets/i18n/en-US.json
@@ -100,6 +100,7 @@
   "alert.integration.source.skywalking": "SkyWalking",
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
+  "alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
   "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
   "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
   "alert.integration.source.volcengine": "Volcengine Monitoring",
diff --git a/web-app/src/assets/i18n/ja-JP.json 
b/web-app/src/assets/i18n/ja-JP.json
index 98e25ac181..69534995f9 100644
--- a/web-app/src/assets/i18n/ja-JP.json
+++ b/web-app/src/assets/i18n/ja-JP.json
@@ -100,6 +100,7 @@
   "alert.integration.source.skywalking": "SkyWalking",
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
+  "alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
   "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
   "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
   "alert.integration.source.volcengine": "火山エンジン監視",
diff --git a/web-app/src/assets/i18n/ko-KR.json 
b/web-app/src/assets/i18n/ko-KR.json
index 0a3fa12580..df7b93bcb9 100644
--- a/web-app/src/assets/i18n/ko-KR.json
+++ b/web-app/src/assets/i18n/ko-KR.json
@@ -100,6 +100,7 @@
   "alert.integration.source.skywalking": "SkyWalking",
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
+  "alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
   "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
   "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
   "alert.integration.source.volcengine": "Volcengine Monitoring",
diff --git a/web-app/src/assets/i18n/pt-BR.json 
b/web-app/src/assets/i18n/pt-BR.json
index e03d13f9e1..e68a10984a 100644
--- a/web-app/src/assets/i18n/pt-BR.json
+++ b/web-app/src/assets/i18n/pt-BR.json
@@ -251,6 +251,7 @@
   "alert.integration.source.prometheus": "Prometheus",
   "alert.integration.source.tencent": "Monitoramento de nuvem Tencent",
   "alert.integration.source.webhook": "PadrãoWebhook",
+  "alert.integration.source.alibabacloud-cms": "Alibaba Cloud Monitor",
   "alert.integration.source.alibabacloud-sls": "AlibabaCloud-SLS",
   "alert.integration.source.huaweicloud-ces": "Huawei Cloud Eye",
   "alert.integration.source.volcengine": "Volcengine",
diff --git a/web-app/src/assets/i18n/zh-CN.json 
b/web-app/src/assets/i18n/zh-CN.json
index d6397daa0f..0eda95a3c0 100644
--- a/web-app/src/assets/i18n/zh-CN.json
+++ b/web-app/src/assets/i18n/zh-CN.json
@@ -100,6 +100,7 @@
   "alert.integration.source.skywalking": "SkyWalking",
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
+  "alert.integration.source.alibabacloud-cms": "阿里云云监控",
   "alert.integration.source.alibabacloud-sls": "阿里云日志服务 SLS",
   "alert.integration.source.huaweicloud-ces": "华为云监控服务",
   "alert.integration.source.volcengine": "火山引擎云监控",
diff --git a/web-app/src/assets/i18n/zh-TW.json 
b/web-app/src/assets/i18n/zh-TW.json
index 70b7847ef0..547b4f290c 100644
--- a/web-app/src/assets/i18n/zh-TW.json
+++ b/web-app/src/assets/i18n/zh-TW.json
@@ -100,6 +100,7 @@
   "alert.integration.source.skywalking": "SkyWalking",
   "alert.integration.source.uptime-kuma": "Uptime Kuma",
   "alert.integration.source.zabbix": "Zabbix",
+  "alert.integration.source.alibabacloud-cms": "阿里雲雲監控",
   "alert.integration.source.alibabacloud-sls": "阿里雲端日誌服務 SLS",
   "alert.integration.source.huaweicloud-ces": "華為雲監控服務",
   "alert.integration.source.volcengine": "火山引擎監控",


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to