LandfallDocs
Integrations · Alerting

Firing CloudWatch alarms into Landfall

This page is about opening incidents automatically the moment a CloudWatch alarm fires, not about reading telemetry. If you want Beacon, Landfall's diagnosis agent, to query CloudWatch metrics and logs once an incident already exists, see Connecting AWS to Landfall instead; the two are independent and most teams eventually set up both.

CloudWatch alarm
state change
SNS → Lambda
forwards the alarm
Landfall
opens an incident

How it works

Landfall accepts an automated trigger on POST /triggers, the same public ingest endpoint a Datadog webhook uses, authenticated by a per-tenant bearer token rather than a signature. It recognizes a CloudWatch alarm payload by shape (an AlarmName and NewStateValue at the top level, the exact JSON CloudWatch publishes to an SNS topic on an alarm action) and opens a new incident from it: title, severity hint, and the metric/namespace/dimensions as entity context, all as provenance on the incident's first timeline event.

Plain SNS HTTPS subscriptions can't attach an Authorization header, so the recipe below uses a small forwarding Lambda between SNS and Landfall, the standard pattern for exactly this gap, and about ten lines of code with nothing to maintain.

What you'll need

  • AWS credentials that can create an SNS topic, a Lambda function, and its execution role.
  • An admin seat on your Landfall organization, to mint the ingest token below.
  • At least one CloudWatch alarm already configured (this guide doesn't create one).

1. Mint a trigger ingest token

This doesn't have a settings-page UI yet, so it's two API calls, made once with your own Landfall account: sign in to get a session token, then use that session token to mint the actual ingest token.

If your organization signs in with email and password (the default for a self-serve organization), both calls are plain curl, start to finish:

Terminal
SESSION=$(curl -s -X POST https://api.landfalls.ai/auth/login \
  -H "Content-Type: application/json" \
  -d '{"email":"<you>","password":"<your password>","orgSlug":"<your-org>"}' \
  | jq -r .accessToken)

curl -X POST https://api.landfalls.ai/o/<your-org>/triggers/ingest-token \
  -H "Authorization: Bearer $SESSION"

Signing in this way needs an admin seat on the organization, same as the settings pages that don't exist for this yet. If your organization signs in through SSO instead, there is no password to hand curl: sign in normally in your browser, then open devtools and read localStorage.getItem('landfall.accessToken') for a session token to use as $SESSION above.

Shown exactly once

The ingest-token response is { "token": "…" }. It is a separate, long-lived credential from the session token above, scoped to opening incidents in this one organization and nothing else. Landfall stores only its hash, so a lost token means minting a new one, which immediately invalidates the old one; there is no dual-valid window. Save it in AWS Secrets Manager or SSM Parameter Store, not in the Lambda's source.

2. Create an SNS topic and subscribe your alarms to it

Standard CloudWatch alarm setup. If you already have an SNS topic your alarms notify, skip to the next step and reuse it.

Terminal
aws sns create-topic --name landfall-incident-triggers

aws cloudwatch put-metric-alarm --alarm-name <your-alarm> \
  --alarm-actions <the topic arn just created> \
  # ...the rest of your existing alarm definition

3. Deploy the forwarder Lambda

Subscribed to the SNS topic, forwarding the alarm's own JSON straight through. No reshaping needed, since it already carries the exact fields Landfall expects.

index.mjs
export const handler = async (event) => {
  const token = process.env.LANDFALL_INGEST_TOKEN;

  for (const record of event.Records) {
    // CloudWatch's own alarm-notification JSON, already the shape Landfall
    // expects, forwarded as-is rather than re-parsed or rebuilt.
    const alarmPayload = record.Sns.Message;

    const res = await fetch('https://api.landfalls.ai/triggers', {
      method: 'POST',
      headers: {
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
      body: alarmPayload,
    });

    if (!res.ok) {
      // Log and move on: one bad delivery shouldn't fail the whole batch.
      console.error('Landfall trigger ingest failed', res.status, await res.text());
    }
  }
};
Terminal
zip function.zip index.mjs

aws lambda create-function --function-name landfall-trigger-forwarder \
  --runtime nodejs20.x --handler index.handler --zip-file fileb://function.zip \
  --role <an execution role with basic Lambda logging permissions> \
  --environment "Variables={LANDFALL_INGEST_TOKEN=<the token from step 1>}"

aws lambda add-permission --function-name landfall-trigger-forwarder \
  --statement-id sns-invoke --action lambda:InvokeFunction \
  --principal sns.amazonaws.com --source-arn <the topic arn from step 2>

aws sns subscribe --topic-arn <the topic arn from step 2> \
  --protocol lambda --notification-endpoint <the function arn just created>
Only want ALARM state, not every OK recovery too?

Landfall has no way to know an OK notification is the recovery for an alarm it already opened: every delivery becomes its own new incident, and there's no automatic linking or closing. Add a one-line filter in the handler (if (JSON.parse(alarmPayload).NewStateValue !== 'ALARM') continue;) if you'd rather only open incidents on the way into an alarm state, not out of it.

4. Test it

Publish a test notification straight to the topic and confirm an incident appears:

Terminal
aws sns publish --topic-arn <the topic arn from step 2> \
  --message '{
    "AlarmName": "landfall-test-alarm",
    "NewStateValue": "ALARM",
    "NewStateReason": "Threshold crossed",
    "StateChangeTime": "2026-01-01T00:00:00.000Z",
    "Trigger": { "MetricName": "5xxErrorRate", "Namespace": "AWS/CloudFront" }
  }'

A new incident opens in your organization within a few seconds, titled landfall-test-alarm, with the metric and namespace above carried as entity context. Trigger a real alarm (or force one) once this works to confirm the end-to-end path.

Reference

EndpointPOST https://api.landfalls.ai/triggers
AuthAuthorization: Bearer <ingest token> identifies the organization directly; there is no separate org path parameter
BodyCloudWatch's own alarm-notification JSON (the SNS Message content), forwarded unmodified
Each deliveryOpens one new incident: no dedup, no auto-close on recovery
Rejected payloads422, no incident created; a missing or invalid token is a uniform 401
Also acceptsA Datadog or Coralogix alert payload on the same endpoint with the same token — this isn't a CloudWatch-only surface. See Datadog & Coralogix alerts for those templates.