AWS is the one estate where you cannot get away with a single ingest path. CloudWatch alarms come through SNS; everything else worth paging on — ECS task failures, GuardDuty findings, AWS Health events, Auto Scaling actions — does not come through CloudWatch at all. Wire only the first and you will miss the events that matter most.
Two ingest paths, and why you need both
alarm"] --> SNS["SNS topic"] end subgraph P2["Path 2 — events"] ECS["ECS task
state change"] --> EB["EventBridge
rule"] GD["GuardDuty
finding"] --> EB HL["AWS Health
event"] --> EB AS["Auto Scaling"] --> EB end SNS --> W1["/api/webhooks/cloudwatch"] EB --> W2["/api/webhooks/eventbridge"] EB -.->|"or via SNS"| W2 W1 & W2 --> N["One normalized incident"] style N fill:#f4f6ff,stroke:#a78bfa,stroke-width:2px
Both endpoints accept the SNS envelope or a bare event, because EventBridge can
deliver either directly or through a topic and you should not have to care which you chose.
The alarm is unwrapped from the SNS Message string automatically.
The subscription handshake is deliberately not automatic
When you subscribe a topic, SNS first posts a SubscriptionConfirmation
carrying a SubscribeURL. We do not fetch it. Fetching a URL
that arrived in a request body is how a service becomes someone else's HTTP client
— anyone who can reach the endpoint could make the API issue a GET wherever they
liked. The confirmation is reported instead, and a person opens it once they recognise
the topic ARN.
What arrives, and what it becomes
| AWS concept | Becomes | Note |
|---|---|---|
AlarmName | alert name | Also mined for severity — an alarm named *-critical or sev1-* is read as critical. |
NewStateValue | severity / resolved | OK resolves the incident; INSUFFICIENT_DATA is a warning, not an alarm. |
Trigger.Dimensions | labels | This is what your match rules bind to. LoadBalancer, FunctionName, ClusterName. |
AlarmArn | external id + region | The region is parsed out of the ARN when Region is absent. |
GuardDuty severity | severity | Numeric 0–10, mapped: ≥7 critical, ≥4 high, else warning. |
detail-type + source | alert name | EventBridge events have no severity of their own, so it comes from what the event is. |
The dimensions row is the one to get right. CloudWatch's service identity lives in the
dimensions rather than in a field called "service", so the normalizer picks the dimension
that usually identifies a service — ServiceName, then
FunctionName, DBInstanceIdentifier, LoadBalancer,
ClusterName, QueueName — and falls back to the namespace. If
your naming differs, a match rule on the raw dimension label still works.
Reading AWS back
Six step types, and credentials come from boto3's own chain — task role, instance role, environment — never from a step's parameters. That constraint matters more than it sounds: runbook editing is a much lower bar than infrastructure access, so if a step could carry credentials, anyone who could edit a runbook could point the agent at another account.
| Step | What it answers |
|---|---|
cloudwatch.metric | One metric over a window, with dimensions and a statistic. The AWS equivalent of prometheus.range. |
cloudwatch.alarms | What else is in ALARM right now — often the fastest way to see that this is one symptom of something wider. |
cloudwatch.logs | Recent matching lines from a log group, with a filter pattern. |
ecs.services | Desired versus running, deployments in flight with their rollout state, and the last five service events. The ECS answer to k8s.pods. |
elb.target_health | Which targets are healthy and why the unhealthy ones are not. Often a better first signal than a scrape. |
cloud.health | Is AWS having a problem of its own? One step, and occasionally the entire answer. |
A caveat on AWS Health
The Health API needs a Business or Enterprise support plan. On accounts without one the step reports exactly that and is skipped — it does not silently return "nothing wrong", which would be a dangerous thing to conclude from an API you cannot call.
A worked ECS incident
The same error-rate shape as the Kubernetes version, translated. Note that only the platform steps change; the reasoning steps are identical, which is the point of keeping the runbook model platform-agnostic.
are failing health checks — established.
That the deployment caused it — assumed."
That last note is the same discipline as everywhere else: a rollout in progress and failing
targets in the same window is correlation. It caps at 0.75 until something establishes the
link — which, on AWS, the deployment's own rolloutStateReason often does,
because ECS will say "tasks failed to start" in as many words.
Getting it wired
- One SNS topic for alarms, subscribed to the webhook with your token in the query string. Confirm the subscription by hand the first time.
- One EventBridge rule with a pattern covering the sources you care about
— start with
aws.ecsandaws.health, addaws.guarddutyif security wants the findings triaged. - A read-only IAM role for the task:
cloudwatch:GetMetricData,cloudwatch:DescribeAlarms,logs:FilterLogEvents,ecs:DescribeServices,elasticloadbalancing:DescribeTargetHealth, andhealth:DescribeEventsif you have the support plan. Nothing that writes. - Check the Integrations page before writing runbooks. It probes each
system and tells you what is reachable, so a step that will come back
unavailableis visible before you depend on it.
Worth stating plainly
The AWS connectors are built against the documented APIs and tested end to end through the product, but they have not been run against a live production account of yours or anyone else's. The read steps fail safe — an unreachable or misconfigured system reports that rather than guessing. The normalizers are the part worth checking against one real payload from your estate, and we would rather you did that in week one than discover a field mismatch during an incident.
Next in this series
InsightResolve on Azure — the easiest of the clouds to wire in, because Azure actually standardised its alert payload.