March 04, 2025
When an alarm goes off at 3 AM, every second counts. Navigating through the AWS Console can be a frustrating experience when troubleshooting a critical issue. What if you could send your on-call engineers directly to the exact CloudWatch alarm page they need, bypassing the maze of AWS Console navigation?
This post demonstrates how to build a system that creates shortcut links to AWS resources through AWS Identity Center (formerly AWS SSO), enabling your team to go from alert notification to viewing the relevant resource in seconds.
Traditional CloudWatch alarm notifications contain basic information such as:
OK
, ALARM
, INSUFFICIENT_DATA
)However, they don’t provide a direct way to access the resource. Engineers receiving these alerts must:
This process consumes valuable minutes that could be better spent addressing the actual issue.
This solution streamlines incident response by creating authenticated shortcut links to specific AWS resources. These links handle the entire authentication process and navigation, taking engineers directly to the relevant resource with a single click.
Here’s the high-level workflow:
The result? Engineers can view the exact resource in seconds rather than minutes.
The implementation suggested in this article will generate the shortcut link and send it to a Discord channel, which looks like this:
However, you can modify the Lambda code to send the alert to a different channel such as Slack, PagerDuty, or email.
Before diving into implementation, it’s important to understand the technology that makes this possible. AWS IAM Identity Center provides a centralized way to manage access across multiple AWS accounts. For our shortcut linking solution, it offers three key capabilities:
When an engineer clicks our shortcut link, here’s what happens behind the scenes:
This eliminates all manual navigation steps, saving critical time during incidents.
To create shortcut links that work with your AWS Identity Center setup, you’ll need two key values:
SSO Start URL (ssoStartUrl
): The entry point to your AWS Identity Center portal.
ssoStartUrl
https://d-xxxxxxxxxx.awsapps.com/start
SSO Role Name (ssoRoleName
): The permission set name that users will assume in the target AWS account.
ssoRoleName
These values will be passed to our Lambda function as environment variables, making them easy to update if your Identity Center configuration changes.
Let’s build this solution using AWS CDK. We’ll create these components:
Let’s break down each component:
First, set up Discord to receive our enhanced notifications via a Webhook:
Note that webhook URLs should be treated as secrets since they allow posting to your Discord channel.
Let’s build our solution with CDK by creating a MonitoringStack
that contains our Discord webhook secret, SNS topic, Lambda function, and CloudWatch alarm for monitoring DynamoDB throttling:
import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as lambda from 'aws-cdk-lib/aws-lambda';
import * as sns from 'aws-cdk-lib/aws-sns';
import * as subs from 'aws-cdk-lib/aws-sns-subscriptions';
import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { SnsAction } from 'aws-cdk-lib/aws-cloudwatch-actions';
interface MonitoringStackProps extends cdk.StackProps {
tableName: string;
ssoStartUrl: string;
ssoRoleName: string;
}
export class MonitoringStack extends cdk.Stack {
constructor(scope: Construct, id: string, props: MonitoringStackProps) {
super(scope, id, props);
// Store Discord webhook URL securely in Secrets Manager
const discordWebhookSecret = new secretsmanager.Secret(this, 'DiscordWebhookSecret', {
secretName: 'discord/webhook',
});
// Create SNS topic that will receive all our CloudWatch alarms
const alarmTopic = new sns.Topic(this, 'AlarmTopic', {
topicName: 'cloud-watch-alarms',
});
// Create Lambda function to process notifications and create shortcut links
const notifyLambda = new lambda.Function(this, 'NotifyLambda', {
runtime: lambda.Runtime.NODEJS_22_X,
handler: 'index.handler',
code: lambda.Code.fromAsset('lambda'),
environment: {
// Pass the secret ARN and SSO values to the Lambda
DISCORD_WEBHOOK_SECRET_ARN: discordWebhookSecret.secretArn,
SSO_START_URL: props.ssoStartUrl,
SSO_ROLE_NAME: props.ssoRoleName,
},
});
// Grant the Lambda permission to read the webhook secret
discordWebhookSecret.grantRead(notifyLambda);
// Subscribe the Lambda to the SNS topic
alarmTopic.addSubscription(new subs.LambdaSubscription(notifyLambda));
// As an example, monitor one of your DynamoDB tables
const table = dynamodb.Table.fromTableName(
this,
'MonitoredTable',
props.tableName
);
// Set an alarm on the ReadThrottleEvents metric
const readThrottleAlarm = new cloudwatch.Alarm(this, 'TableReadThrottleAlarm', {
metric: new cloudwatch.Metric({
namespace: 'AWS/DynamoDB',
metricName: 'ReadThrottleEvents',
dimensionsMap: {
TableName: props.tableName,
},
statistic: 'Sum',
}),
threshold: 1, // Alarm on a single throttle event
evaluationPeriods: 1, // In a single period
alarmName: `${props.tableName}-ReadThrottleAlarm`,
});
// When the alarm triggers, publish a message to the SNS topic
readThrottleAlarm.addAlarmAction(new SnsAction(alarmTopic));
}
}
This CDK application:
Creates the notification infrastructure:
Sets up DynamoDB monitoring:
Here’s how to deploy this stack with your specific SSO values:
// In your bin/<app-name>.ts file or equivalent
const app = new cdk.App();
new MonitoringStack(app, 'MonitoringStack', {
tableName: 'your-dynamodb-table-name',
ssoStartUrl: 'https://d-xxxxxxxxxx.awsapps.com/start',
ssoRoleName: 'AWSAdministratorAccess', // Or another permission set name
});
After deployment, add your Discord Webhook URL as the secret value in AWS Secrets Manager.
Now for the core of our solution—the Lambda function that processes CloudWatch alarm notifications and creates shortcut links. Note that you can spend time customizing how the alert renders in Discord to meet your specific needs. Choose your own colors, emojis, layout, and more by customizing the Lambda function!
import { SecretsManagerClient, GetSecretValueCommand } from "@aws-sdk/client-secrets-manager";
// Emoji for Discord message based on alarm state
const getStateDiscordEmoji = (state) => {
if (state === 'OK') return ':green_square:';
if (state === 'ALARM') return ':red_square:';
if (state === 'INSUFFICIENT_DATA') return ':yellow_square:';
return ':grey_question:';
};
// Color for Discord message based on alarm state
const getStateDiscordColor = (state) => {
if (state === 'OK') return 65280;
if (state === 'ALARM') return 16711680;
if (state === 'INSUFFICIENT_DATA') return 16776960;
return 13421772;
};
// Helper to extract parts of the alarm ARN
const parseAlarmArn = (alarmArn) => {
const match = alarmArn.match(/^arn:aws:cloudwatch:(.+):(.+):alarm:(.+)$/);
if (!match) {
throw new Error(`Invalid alarm ARN: ${alarmArn}`);
}
return {
region: match[1],
accountId: match[2],
alarmName: match[3],
};
};
// Helper functions for building properly formatted alarm URL
const getAlarmUrl = (alarmName, region) => {
const alarmUrl = new URL('https://console.aws.amazon.com/cloudwatch/home');
alarmUrl.searchParams.set('region', region);
alarmUrl.hash = `alarmsV2:alarm/${encodeURIComponent(alarmName).replaceAll('%', '$')}`;
return alarmUrl.href;
};
// Helper function for building properly formatted SSO shortcut URL
const getSsoShortcutUrl = (accountId, roleName, destinationUrl) => {
const hashParams = new URLSearchParams({
account_id: accountId,
role_name: roleName,
destination: destinationUrl,
});
const ssoShortcutUrl = new URL(`${process.env.SSO_START_URL}/`);
ssoShortcutUrl.hash = `/console?${hashParams}`;
return ssoShortcutUrl.href;
};
// Initialize the client outside the handler for better performance
const secretsManagerClient = new SecretsManagerClient();
export const handler = async (event) => {
// Parse the SNS message from CloudWatch
const {
AWSAccountId,
AlarmArn,
AlarmName,
NewStateReason,
NewStateValue,
StateChangeTime,
Trigger,
} = JSON.parse(event.Records[0].Sns.Message);
const { region } = parseAlarmArn(AlarmArn);
const stateEmoji = getStateDiscordEmoji(NewStateValue);
const stateColor = getStateDiscordColor(NewStateValue);
// Build the direct link to this alarm in CloudWatch
const alarmUrl = getAlarmUrl(AlarmName, region);
// Create the Identity Center shortcut link
const ssoRoleName = process.env.SSO_ROLE_NAME;
const alarmSsoShortcutUrl = getSsoShortcutUrl(AWSAccountId, ssoRoleName, alarmUrl);
// Retrieve the Discord webhook URL from Secrets Manager
const { SecretString: webhookUrl } = await secretsManagerClient.send(
new GetSecretValueCommand({
SecretId: process.env.DISCORD_WEBHOOK_SECRET_ARN
})
);
const title = `${stateEmoji} **${NewStateValue}**`;
const description = `Alarm **${AlarmName}** entered state **${NewStateValue}** due to "_${NewStateReason}_"`;
// Create a formatted Discord message with the shortcut link
const message = {
content: null,
embeds: [{
title,
description,
url: alarmSsoShortcutUrl,
color: stateColor,
fields: [
{
name: 'Account',
value: AWSAccountId,
inline: true
},
{
name: 'Region',
value: region,
inline: true
},
{
name: 'Time',
value: StateChangeTime,
inline: true
},
{
name: 'Alarm',
value: AlarmName,
inline: true
},
{
name: 'State',
value: NewStateValue,
inline: true
},
{
name: 'Reason',
value: NewStateReason,
inline: true
},
{
name: 'View Alarm',
value: `[Open Alarm in CloudWatch Console](${alarmSsoShortcutUrl})`
},
],
}],
};
// Send the notification to Discord
await fetch(webhookUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(message)
});
};
Here’s how these components create a seamless incident response workflow:
Alert Triggering: A CloudWatch alarm changes state and publishes a notification to the SNS topic.
Link Creation: The Lambda function:
Engineer Response: When an engineer receives the notification:
When an alert comes in, engineers see a nicely formatted Discord message and can access the resource within seconds—dramatically faster than the traditional multi-step navigation process:
This approach isn’t limited to CloudWatch alarms. You can adapt the URL-building logic above to create shortcut links to any AWS resource:
You can also integrate with other notification channels like Slack, Microsoft Teams, or PagerDuty.
When implementing this solution, adhere to these security considerations:
By combining CloudWatch alarms with AWS Identity Center shortcut linking, you create a powerful incident response tool that:
This approach not only improves your team’s efficiency but also reduces the stress of on-call rotations by streamlining the response process.
The next time an alarm wakes someone up at 3 AM, they’ll be one click away from the exact resource they need to fix—and that makes all the difference.
Happy building!
Written by Daniel Worsnup. All my links.