Configure AWS CloudWatch to Send Webhooks
Configure AWS CloudWatch alarms to send webhook notifications to Harness AI SRE using Amazon SNS with HTTPS subscription.
Before you begin
- Harness webhook endpoint: Create an AWS CloudWatch webhook in Harness AI SRE using the AWS CloudWatch webhook template.
- AWS permissions: Access to create SNS topics, CloudWatch alarms, and manage subscriptions.
- Webhook URL: Copy the webhook URL from your Harness webhook configuration.
- CloudWatch alarms documentation: Go to CloudWatch Alarms to understand alarm configuration.
- SNS HTTPS subscriptions: Go to SNS HTTPS Subscriptions for subscription setup details.
Architecture overview
CloudWatch alarms → SNS Topic → HTTPS Subscription → Harness Webhook
CloudWatch does not send webhooks directly. Use Amazon SNS as an intermediary:
- CloudWatch alarm state changes
- Alarm publishes to SNS topic
- SNS sends HTTPS POST to Harness webhook
- Harness processes the alert
Create SNS topic
Navigate to SNS
- Open AWS Console and go to Simple Notification Service (SNS)
- Click Topics → Create topic
Configure topic
- Standard topic
- FIFO topic
- Type: Standard
- Name:
harness-ai-sre-alerts - Display name:
Harness AI SRE Alerts - Encryption: (Optional) Enable encryption at rest
- Access policy: Default (allow publishers)
FIFO topics are not recommended for CloudWatch alarms as they require message group IDs.
Use Standard topic instead.
Save topic
Click Create topic and note the Topic ARN.
Create HTTPS subscription
Add subscription to topic
- In the SNS topic details, click Create subscription
Configure subscription
- Basic configuration
- With filter policy
- Protocol: HTTPS
- Endpoint: Your Harness webhook URL
https://<your-harness-instance>/gateway/ai-sre/api/webhooks/<webhook-id>
- Enable raw message delivery: Uncheck (keep message wrapper)
- Redrive policy: (Optional) Configure DLQ for failed deliveries
- Protocol: HTTPS
- Endpoint: Your Harness webhook URL
- Subscription filter policy: JSON filter to process only specific alarms
{
"AlarmName": [
{"prefix": "Production-"}
],
"NewStateValue": [
"ALARM"
]
}
This filters to only production alarms in ALARM state.
Confirm subscription
- Click Create subscription
- SNS sends a confirmation request to your Harness webhook
- Harness must respond with subscription confirmation
Note: Configure your Harness webhook to automatically confirm SNS subscriptions by responding to SubscribeURL in the payload.
Create CloudWatch alarm
Navigate to CloudWatch
- Open AWS Console and go to CloudWatch
- Click Alarms → Create alarm
Select metric
- EC2 CPU usage
- RDS connections
- Lambda errors
- Click Select metric
- Choose EC2 → Per-Instance Metrics
- Select CPUUtilization for your instance
- Click Select metric
Conditions:
- Threshold type: Static
- Whenever CPUUtilization is...: Greater than
80 - Datapoints to alarm:
2 out of 2
- Click Select metric
- Choose RDS → Per-Database Metrics
- Select DatabaseConnections for your DB instance
- Click Select metric
Conditions:
- Threshold type: Static
- Whenever DatabaseConnections is...: Greater than
100 - Datapoints to alarm:
3 out of 5
- Click Select metric
- Choose Lambda → Per-Function Metrics
- Select Errors for your function
- Click Select metric
Conditions:
- Threshold type: Static
- Whenever Errors is...: Greater than
10 - Datapoints to alarm:
1 out of 1
Configure actions
In the Configure actions step:
- Notification:
- Alarm state trigger: In alarm
- Send notification to: Select your SNS topic
harness-ai-sre-alerts
- Additional actions: (Optional) Configure Auto Scaling or EC2 actions
Set alarm details
- Alarm name:
Production-EC2-HighCPU - Alarm description:
EC2 instance CPU usage above 80% - Treat missing data as: Choose appropriate option (default:
missing)
Create alarm
Click Create alarm.
Configure field mapping in Harness
In your Harness webhook configuration, map CloudWatch/SNS payload fields to alert properties.
CloudWatch alarm SNS payload structure
{
"Type": "Notification",
"MessageId": "abc-123-def-456",
"TopicArn": "arn:aws:sns:us-east-1:123456789012:harness-ai-sre-alerts",
"Subject": "ALARM: \"Production-EC2-HighCPU\" in US East (N. Virginia)",
"Message": "{\"AlarmName\":\"Production-EC2-HighCPU\",\"AlarmDescription\":\"EC2 instance CPU usage above 80%\",\"AWSAccountId\":\"123456789012\",\"AlarmConfigurationUpdatedTimestamp\":\"2025-07-01T10:00:00.000Z\",\"NewStateValue\":\"ALARM\",\"NewStateReason\":\"Threshold Crossed: 2 datapoints [85.0 (01/07/25 10:00:00), 90.0 (01/07/25 10:05:00)] were greater than the threshold (80.0).\",\"StateChangeTime\":\"2025-07-01T10:10:00.000Z\",\"Region\":\"US East (N. Virginia)\",\"AlarmArn\":\"arn:aws:cloudwatch:us-east-1:123456789012:alarm:Production-EC2-HighCPU\",\"OldStateValue\":\"OK\",\"OKActions\":[],\"AlarmActions\":[\"arn:aws:sns:us-east-1:123456789012:harness-ai-sre-alerts\"],\"InsufficientDataActions\":[],\"Trigger\":{\"MetricName\":\"CPUUtilization\",\"Namespace\":\"AWS/EC2\",\"StatisticType\":\"Statistic\",\"Statistic\":\"AVERAGE\",\"Unit\":null,\"Dimensions\":[{\"value\":\"i-1234567890abcdef0\",\"name\":\"InstanceId\"}],\"Period\":300,\"EvaluationPeriods\":2,\"ComparisonOperator\":\"GreaterThanThreshold\",\"Threshold\":80.0,\"TreatMissingData\":\"missing\",\"EvaluateLowSampleCountPercentile\":\"\"}}",
"Timestamp": "2025-07-01T10:10:00.000Z",
"SignatureVersion": "1",
"Signature": "...",
"SigningCertURL": "...",
"UnsubscribeURL": "..."
}
Note: The Message field contains a JSON string that must be parsed.
Basic field mapping
CloudWatch alarm data is nested in the Message field as a JSON string. Use CEL to parse:
title: "{{webhook.Subject}}"
message: "{{webhook.Message}}"
severity: "ALARM"
source: "cloudwatch"
link: "https://console.aws.amazon.com/cloudwatch"
tags:
- "source:cloudwatch"
- "topic:{{webhook.TopicArn}}"
Advanced field mapping with CEL
Parse the nested JSON message:
// Parse the Message JSON string
parsed_message: webhook.Message.parseJson()
// Extract alarm details
title: parsed_message.AlarmName + " - " + parsed_message.NewStateValue
message: parsed_message.AlarmDescription + "\n\n" +
"State: " + parsed_message.OldStateValue + " → " + parsed_message.NewStateValue + "\n" +
"Reason: " + parsed_message.NewStateReason + "\n" +
"Region: " + parsed_message.Region + "\n" +
"Account: " + parsed_message.AWSAccountId
// Map CloudWatch state to Harness severity
severity: parsed_message.NewStateValue == "ALARM" ? "critical" :
parsed_message.NewStateValue == "INSUFFICIENT_DATA" ? "medium" : "info"
source: "cloudwatch"
link: "https://console.aws.amazon.com/cloudwatch/home?region=" +
parsed_message.Region.toLowerCase().replace(" ", "-").replace("(", "").replace(")", "") +
"#alarmsV2:alarm/" + parsed_message.AlarmName
// Extract metric dimensions as tags
tags: ["source:cloudwatch",
"alarm:" + parsed_message.AlarmName,
"state:" + parsed_message.NewStateValue,
"region:" + parsed_message.Region,
"account:" + parsed_message.AWSAccountId,
"metric:" + parsed_message.Trigger.MetricName,
"namespace:" + parsed_message.Trigger.Namespace] +
(has(parsed_message.Trigger.Dimensions)
? parsed_message.Trigger.Dimensions.map(d, d.name + ":" + d.value)
: [])
// Filter: only ALARM states
filter: parsed_message.NewStateValue == "ALARM"
custom_fields: {
"alarm_arn": parsed_message.AlarmArn,
"account_id": parsed_message.AWSAccountId,
"region": parsed_message.Region,
"metric_name": parsed_message.Trigger.MetricName,
"threshold": string(parsed_message.Trigger.Threshold),
"comparison_operator": parsed_message.Trigger.ComparisonOperator
}
Test the integration
Test with CloudWatch console
- Go to CloudWatch → Alarms
- Select your alarm
- Click Actions → Set alarm state
- Choose In alarm
- Click Confirm
This manually triggers the alarm to test the integration.
Test SNS subscription
aws sns publish \
--topic-arn arn:aws:sns:us-east-1:123456789012:harness-ai-sre-alerts \
--message '{"AlarmName":"Test","NewStateValue":"ALARM","NewStateReason":"Test"}' \
--subject "TEST: Alarm"
Verify in Harness
- Navigate to Alerts in Harness AI SRE
- Check that the alarm appears
- Verify field mapping parsed the message correctly
Available CloudWatch alarm fields
Fields in the parsed Message JSON:
| Field | Description | Example |
|---|---|---|
AlarmName | Alarm name | Production-EC2-HighCPU |
AlarmDescription | Alarm description | EC2 instance CPU usage above 80% |
AWSAccountId | AWS account ID | 123456789012 |
NewStateValue | New alarm state | ALARM, OK, INSUFFICIENT_DATA |
OldStateValue | Previous alarm state | OK, ALARM |
NewStateReason | State change reason | Threshold Crossed: ... |
StateChangeTime | When state changed | 2025-07-01T10:10:00.000Z |
Region | AWS region | US East (N. Virginia) |
AlarmArn | Alarm ARN | arn:aws:cloudwatch:... |
Trigger.MetricName | Metric name | CPUUtilization |
Trigger.Namespace | Metric namespace | AWS/EC2 |
Trigger.Statistic | Statistic type | AVERAGE, SUM, MAXIMUM |
Trigger.Dimensions | Metric dimensions | [{name: "InstanceId", value: "i-..."}] |
Trigger.Period | Evaluation period (seconds) | 300 |
Trigger.EvaluationPeriods | Number of periods | 2 |
Trigger.Threshold | Alarm threshold | 80.0 |
Trigger.ComparisonOperator | Comparison operator | GreaterThanThreshold |
Advanced configuration
Filter by namespace
Only process specific AWS service alarms:
filter: parsed_message.NewStateValue == "ALARM" &&
parsed_message.Trigger.Namespace in ["AWS/EC2", "AWS/RDS", "AWS/Lambda"]
Route by region
Tag and route by AWS region:
tags: ["source:cloudwatch",
"region:" + parsed_message.Region,
"environment:" + (parsed_message.Region.contains("us-east") ? "production" : "staging")]
Extract EC2 instance details
For EC2 alarms, extract instance ID:
instance_id: has(parsed_message.Trigger.Dimensions)
? parsed_message.Trigger.Dimensions.filter(d, d.name == "InstanceId")[0].value
: ""
message: parsed_message.AlarmDescription + "\n\n" +
"Instance: " + instance_id + "\n" +
"Metric: " + parsed_message.Trigger.MetricName
Create composite alert messages
Combine multiple alarm details:
message: "**CloudWatch Alarm**: " + parsed_message.AlarmName + "\n\n" +
"**Description**: " + parsed_message.AlarmDescription + "\n" +
"**State Change**: " + parsed_message.OldStateValue + " → " + parsed_message.NewStateValue + "\n" +
"**Reason**: " + parsed_message.NewStateReason + "\n\n" +
"**Metric Details**:\n" +
"- Namespace: " + parsed_message.Trigger.Namespace + "\n" +
"- Metric: " + parsed_message.Trigger.MetricName + "\n" +
"- Statistic: " + parsed_message.Trigger.Statistic + "\n" +
"- Threshold: " + string(parsed_message.Trigger.Threshold) + "\n" +
"- Comparison: " + parsed_message.Trigger.ComparisonOperator + "\n\n" +
"**AWS Details**:\n" +
"- Account: " + parsed_message.AWSAccountId + "\n" +
"- Region: " + parsed_message.Region + "\n" +
"- Time: " + parsed_message.StateChangeTime
Troubleshooting
SNS subscription not confirming
Cause: Harness webhook not responding to subscription confirmation request.
Solution:
- Check Harness webhook logs for
SubscribeURLin payload - Configure Harness to automatically confirm subscriptions:
// Detect SNS subscription confirmation
filter: webhook.Type != "SubscriptionConfirmation"
// Or handle it separately by fetching SubscribeURL
- Manually confirm via AWS Console: SNS → Subscriptions → Confirm
Message field not parsing
Cause: Message is double-encoded or in unexpected format.
Solution:
- Check raw webhook payload in Harness logs
- Verify Message is a JSON string:
webhook.Message.parseJson() - Handle parsing errors:
parsed_message: webhook.Message.parseJson()
title: has(parsed_message.AlarmName) ? parsed_message.AlarmName : webhook.Subject
Alarms not triggering
Cause: CloudWatch alarm not configured with SNS topic.
Solution:
- Edit alarm in CloudWatch
- Go to Actions section
- Add notification action with your SNS topic ARN
- Ensure alarm state matches trigger (In alarm, OK, Insufficient data)
Signature verification failing
Cause: SNS message signature verification issues.
Solution:
- SNS signs all messages with certificates
- Verify signature using AWS SDK or certificate URL
- Or accept messages without verification if within VPC/private network
Example: Complete integration
AWS SNS topic
- Name:
harness-ai-sre-alerts - Type: Standard
- Subscription:
- Protocol: HTTPS
- Endpoint:
https://app.harness.io/gateway/ai-sre/api/webhooks/wh_abc123 - Status: Confirmed
CloudWatch alarm
- Name:
Production-RDS-HighConnections - Metric:
AWS/RDS→DatabaseConnections - Condition: Greater than 100 for 3 out of 5 datapoints
- Actions: Notify
harness-ai-sre-alertswhen in ALARM state
Harness webhook field mapping
# Parse nested Message JSON
parsed_message: webhook.Message.parseJson()
title: |
parsed_message.AlarmName + " - " + parsed_message.NewStateValue
message: |
**CloudWatch Alarm**: {{parsed_message.AlarmName}}
**Description**: {{parsed_message.AlarmDescription}}
**State**: {{parsed_message.OldStateValue}} → {{parsed_message.NewStateValue}}
**Reason**: {{parsed_message.NewStateReason}}
**Metric**: {{parsed_message.Trigger.MetricName}} ({{parsed_message.Trigger.Namespace}})
**Threshold**: {{parsed_message.Trigger.ComparisonOperator}} {{parsed_message.Trigger.Threshold}}
**Statistic**: {{parsed_message.Trigger.Statistic}} over {{parsed_message.Trigger.Period}}s
**AWS Account**: {{parsed_message.AWSAccountId}}
**Region**: {{parsed_message.Region}}
**Time**: {{parsed_message.StateChangeTime}}
**View in AWS Console**: https://console.aws.amazon.com/cloudwatch/home#alarmsV2:alarm/{{parsed_message.AlarmName}}
severity: |
parsed_message.NewStateValue == "ALARM" ? "critical" :
parsed_message.NewStateValue == "INSUFFICIENT_DATA" ? "medium" : "info"
source: "cloudwatch"
link: |
"https://console.aws.amazon.com/cloudwatch/home#alarmsV2:alarm/" + parsed_message.AlarmName
tags:
- "source:cloudwatch"
- "alarm:{{parsed_message.AlarmName}}"
- "state:{{parsed_message.NewStateValue}}"
- "account:{{parsed_message.AWSAccountId}}"
- "region:{{parsed_message.Region}}"
- "namespace:{{parsed_message.Trigger.Namespace}}"
- "metric:{{parsed_message.Trigger.MetricName}}"
filter: |
webhook.Type == "Notification" &&
parsed_message.NewStateValue == "ALARM"
custom_fields:
alarm_arn: "{{parsed_message.AlarmArn}}"
aws_account: "{{parsed_message.AWSAccountId}}"
aws_region: "{{parsed_message.Region}}"
metric_namespace: "{{parsed_message.Trigger.Namespace}}"
metric_name: "{{parsed_message.Trigger.MetricName}}"
threshold: "{{parsed_message.Trigger.Threshold}}"
Next steps
- Go to Route Alerts to route and deduplicate CloudWatch alarms.
- Go to Use CEL in Webhooks for JSON parsing and filtering.
- Go to AI Agent to enable automated alarm investigation.
- Go to AWS CloudWatch Template for the pre-configured template.
Further reading
AWS Official Documentation
- CloudWatch Alarms - Complete guide to CloudWatch alarm configuration and SNS integration
- SNS HTTPS Subscriptions - HTTPS subscription setup and subscription confirmation process
- CloudWatch and EventBridge - CloudWatch alarm message format and payload structure
- SNS Message Formats - SNS notification wrapper and Message field structure