Instant Slack notifications whenever a billable AWS resource is created — with who created it, estimated daily cost, and estimated monthly cost.
- What This Does
- How It Works
- Architecture
- Supported AWS Services
- Slack Notification Preview
- Project Structure
- Prerequisites
- Deployment Guide
- Environment Variables
- IAM Permissions Required
- EventBridge Rule Pattern
- Cost Pricing Database
- Testing
- Why This Matters
- Contributing
Every time someone in your AWS account creates a resource that costs money, this tool:
- Detects it in real time via CloudTrail + EventBridge
- Identifies who created it — IAM user name, SSO role session, or flags ROOT account usage
- Estimates the cost — daily and monthly based on on-demand pricing
- Sends a Slack alert with full details, severity badge, and direct links to Cost Explorer
No more surprise AWS bills. No more "who spun up that m5.4xlarge and forgot about it."
Someone creates an AWS resource
│
▼
AWS CloudTrail
(logs the API call)
│
▼
Amazon EventBridge
(matches Create* events)
│
▼
AWS Lambda Function
(Python 3.12)
│
┌─────┴──────┐
│ Extracts │
│ • Service │
│ • Who │
│ • Config │
│ • Cost │
└─────┬──────┘
│
▼
Slack Webhook
(instant notification)
The Lambda function runs in under 500ms and uses zero external dependencies — pure Python standard library only. No pip install needed.
┌─────────────────────────────────────────────────────────────────────┐
│ AWS Account │
│ │
│ User/Role creates resource │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ AWS CloudTrail │ ← Logs every API call │
│ │ (Management │ │
│ │ Events) │ │
│ └────────┬────────┘ │
│ │ Create* events │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ Amazon EventBridge │ │
│ │ │ │
│ │ Rule: finops-resource-creation-alert │ │
│ │ Pattern: 26 Create events across 18 AWS services │ │
│ └───────────────────────┬─────────────────────────────────────┘ │
│ │ Invoke │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ AWS Lambda Function │ │
│ │ finops-cost-alert │ │
│ │ Python 3.12 | 256MB | 30s timeout │ │
│ │ │ │
│ │ 1. Parse CloudTrail event detail │ │
│ │ 2. Identify who created the resource │ │
│ │ 3. Look up pricing (26 services, 100+ instance types) │ │
│ │ 4. Calculate daily + monthly cost estimate │ │
│ │ 5. Build rich Slack Block Kit message │ │
│ │ 6. POST to Slack webhook │ │
│ └───────────────────────┬─────────────────────────────────────┘ │
│ │ │
└──────────────────────────┼──────────────────────────────────────────┘
│
▼
┌────────────────────────┐
│ Slack Channel │
│ #aws-cost-alerts │
│ │
│ 🔴 HIGH SPEND │
│ New EC2 m5.4xlarge │
│ Created by: john.doe │
│ Est: $4.61/day │
│ Est: $138.24/month │
└────────────────────────┘
The Lambda has a built-in pricing database covering 26 resource types across 18 AWS services:
| Service | Event Detected | Pricing Model |
|---|---|---|
| EC2 | RunInstances |
Per instance-hour (100+ instance types) |
| AWS Lambda | CreateFunction |
Per request + GB-second |
| SageMaker | CreateNotebookInstance, CreateEndpoint |
Per instance-hour |
| Service | Event Detected | Pricing Model |
|---|---|---|
| RDS | CreateDBInstance |
Per instance-hour + storage |
| Aurora | CreateDBCluster |
Per instance-hour (Multi-AZ aware) |
| ElastiCache | CreateCacheCluster, CreateReplicationGroup |
Per node-hour |
| DynamoDB | CreateTable |
Per request or provisioned capacity |
| Redshift | CreateCluster |
Per node-hour |
| OpenSearch | CreateDomain |
Per instance-hour |
| Service | Event Detected | Pricing Model |
|---|---|---|
| EBS | CreateVolume |
Per GB-month (gp2/gp3/io1/io2/st1/sc1) |
| S3 | CreateBucket |
Per GB-month + requests |
| EFS | CreateFileSystem |
Per GB-month |
| Service | Event Detected | Pricing Model |
|---|---|---|
| NAT Gateway | CreateNatGateway |
Per hour + per GB processed |
| ALB / NLB | CreateLoadBalancer |
Per hour + LCU/NLCU |
| CloudFront | CreateDistribution |
Per request + data transfer |
| VPN | CreateVpnConnection |
Per hour |
| Service | Event Detected | Pricing Model |
|---|---|---|
| Kinesis | CreateStream |
Per shard-hour |
| Glue | CreateJob |
Per DPU-hour |
| SNS | CreateTopic |
Per request |
| SQS | CreateQueue |
Per request |
| Service | Event Detected | Pricing Model |
|---|---|---|
| Secrets Manager | CreateSecret |
Per secret per month |
| EKS | CreateCluster |
Per cluster-hour |
╔══════════════════════════════════════════════════════════╗
║ 🖥️ New AWS Resource Created — Amazon EC2 ║
╠══════════════════════════════════════════════════════════╣
║ ║
║ Service Amazon EC2 ║
║ Severity 🔴 HIGH SPEND ║
║ Resource ID i-0abc123def456789a, i-0abc123def... ║
║ Configuration 2x `m5.xlarge` ║
║ Region us-east-1 ║
║ Event RunInstances ║
║ Account Production (123456789012) ║
║ Time (UTC) 2026-03-20T10:30:00Z ║
║ ║
╠══════════════════════════════════════════════════════════╣
║ 👤 Created By ║
║ Name: john.doe ║
║ Type: IAM User ║
║ ARN: arn:aws:iam::123456789012:user/john.doe ║
╠══════════════════════════════════════════════════════════╣
║ 💰 Estimated Cost ║
║ Per day: ~$9.22 ║
║ Per month: ~$276.48 ║
║ • EBS: ~$0.64/mo per 8GB gp3 root volume (default) ║
║ ║
║ ⚠️ These are estimates based on on-demand pricing ║
║ in us-east-1. Actual charges depend on usage, ║
║ data transfer, snapshots, and licensing. Check ║
║ AWS Cost Explorer for actuals once billing ║
║ data is available (~24-48h). ║
╠══════════════════════════════════════════════════════════╣
║ 📊 View in Cost Explorer | 📋 View in CloudTrail ║
║ 🏷️ Tag your resources to track costs by team/project ║
╚══════════════════════════════════════════════════════════╝
Severity levels based on estimated monthly cost:
- 🔴 HIGH SPEND — more than $500/month
- 🟠 MEDIUM SPEND — $20–$500/month
- 🟡 LOW SPEND — under $20/month
- 🔵 PAY-PER-USE — no fixed cost (S3, Lambda, DynamoDB)
aws-finops-cost-alert/
│
├── lambda_function.py # Main Lambda handler — all logic in one file
│ # No external dependencies — pure Python stdlib
│
├── setup/
│ ├── iam-policy.json # IAM policy for Lambda execution role
│ ├── eventbridge-pattern.json # EventBridge rule event pattern
│ └── deploy.sh # One-script CLI deployment
│
├── tests/
│ └── test_events/
│ ├── ec2_test.json # Sample EC2 RunInstances event
│ ├── rds_test.json # Sample RDS CreateDBInstance event
│ └── s3_test.json # Sample S3 CreateBucket event
│
└── README.md
- AWS account with CloudTrail enabled
- AWS CLI configured (
aws configure) - Slack workspace with an Incoming Webhook URL
- Lambda function already created with the code uploaded
In the AWS Console go to Lambda → finops-cost-alert → Code and paste the contents of lambda_function.py, then click Deploy.
Or via CLI:
zip finops_cost_alert.zip lambda_function.py
aws lambda update-function-code \
--function-name finops-cost-alert \
--zip-file fileb://finops_cost_alert.zipIn Lambda → Configuration → Environment variables add:
| Key | Value |
|---|---|
SLACK_WEBHOOK_URL |
https://hooks.slack.com/services/YOUR/WEBHOOK/URL |
ACCOUNT_NAME |
Production (or whatever your account name is) |
Copy and paste this entire block into your terminal:
# Auto-detect account and region
ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=$(aws configure get region)
LAMBDA_ARN=$(aws lambda get-function \
--function-name finops-cost-alert \
--query 'Configuration.FunctionArn' \
--output text)
echo "Account: $ACCOUNT_ID | Region: $REGION"
# Create IAM policy
aws iam create-policy \
--policy-name finops-cost-alert-policy \
--policy-document '{
"Version":"2012-10-17",
"Statement":[
{"Effect":"Allow","Action":["logs:CreateLogGroup","logs:CreateLogStream","logs:PutLogEvents"],"Resource":"arn:aws:logs:*:*:*"},
{"Effect":"Allow","Action":["cloudtrail:LookupEvents","cloudtrail:GetTrailStatus"],"Resource":"*"},
{"Effect":"Allow","Action":["pricing:GetProducts","pricing:DescribeServices"],"Resource":"*"},
{"Effect":"Allow","Action":"sts:GetCallerIdentity","Resource":"*"}
]
}'
# Attach policy to Lambda role
LAMBDA_ROLE=$(aws lambda get-function-configuration \
--function-name finops-cost-alert \
--query 'Role' --output text | cut -d'/' -f2)
aws iam attach-role-policy \
--role-name $LAMBDA_ROLE \
--policy-arn arn:aws:iam::$ACCOUNT_ID:policy/finops-cost-alert-policy
# Create EventBridge rule
aws events put-rule \
--name finops-resource-creation-alert \
--description "FinOps Slack alert on billable resource creation" \
--event-pattern '{"source":["aws.ec2","aws.rds","aws.s3","aws.elasticloadbalancing","aws.elasticache","aws.eks","aws.kinesis","aws.es","aws.redshift","aws.efs","aws.secretsmanager","aws.cloudfront","aws.glue","aws.sagemaker","aws.lambda","aws.dynamodb","aws.sns","aws.sqs"],"detail-type":["AWS API Call via CloudTrail"],"detail":{"eventName":["RunInstances","CreateDBInstance","CreateDBCluster","CreateNatGateway","CreateLoadBalancer","CreateVolume","CreateBucket","CreateFunction","CreateFunction20150331","CreateCacheCluster","CreateReplicationGroup","CreateCluster","CreateStream","CreateDomain","CreateFileSystem","CreateSecret","CreateTable","CreateDistribution","CreateDistribution2020_05_31","CreateJob","CreateNotebookInstance","CreateEndpoint","CreateTopic","CreateQueue","CreateVpnConnection","CreateVpnGateway"]}}' \
--state ENABLED \
--region $REGION
# Add Lambda as target
aws events put-targets \
--rule finops-resource-creation-alert \
--targets "Id=FinOpsLambdaTarget,Arn=$LAMBDA_ARN" \
--region $REGION
# Grant EventBridge permission to invoke Lambda
RULE_ARN=$(aws events describe-rule \
--name finops-resource-creation-alert \
--region $REGION \
--query 'Arn' --output text)
aws lambda add-permission \
--function-name finops-cost-alert \
--statement-id EventBridgeInvokeFinOps \
--action lambda:InvokeFunction \
--principal events.amazonaws.com \
--source-arn $RULE_ARN
echo "✅ Setup complete!"ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
REGION=$(aws configure get region)
aws lambda invoke \
--function-name finops-cost-alert \
--payload "{
\"version\":\"0\",
\"source\":\"aws.ec2\",
\"account\":\"$ACCOUNT_ID\",
\"region\":\"$REGION\",
\"detail-type\":\"AWS API Call via CloudTrail\",
\"detail\":{
\"eventName\":\"RunInstances\",
\"eventSource\":\"ec2.amazonaws.com\",
\"awsRegion\":\"$REGION\",
\"recipientAccountId\":\"$ACCOUNT_ID\",
\"eventTime\":\"2026-03-20T10:30:00Z\",
\"userIdentity\":{
\"type\":\"IAMUser\",
\"arn\":\"arn:aws:iam::$ACCOUNT_ID:user/test-user\",
\"userName\":\"test-user\"
},
\"requestParameters\":{
\"instanceType\":\"m5.xlarge\",
\"instancesSet\":{\"maxCount\":2}
},
\"responseElements\":{
\"instancesSet\":{
\"items\":[{\"instanceId\":\"i-0abc123test456789\"}]
}
}
}
}" \
--cli-binary-format raw-in-base64-out \
--output json \
response.json && cat response.jsonCheck your Slack channel — you should see a notification within seconds.
| Variable | Required | Description | Example |
|---|---|---|---|
SLACK_WEBHOOK_URL |
✅ Yes | Slack incoming webhook URL | https://hooks.slack.com/services/T.../B.../xxx |
ACCOUNT_NAME |
Optional | Friendly account name shown in alerts | Production |
CURRENCY |
Optional | Currency symbol (default: USD) | USD |
The Lambda execution role needs:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
"Resource": "arn:aws:logs:*:*:*"
},
{
"Effect": "Allow",
"Action": [
"cloudtrail:LookupEvents",
"cloudtrail:GetTrailStatus"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"pricing:GetProducts",
"pricing:DescribeServices"
],
"Resource": "*"
}
]
}The rule listens for 26 specific Create events across 18 AWS services. CloudTrail must be enabled for management events (enabled by default in all accounts).
Important: EventBridge uses CloudTrail events which may have a delay of 5–15 minutes between resource creation and notification delivery. This is a CloudTrail limitation, not a Lambda limitation.
The Lambda contains an offline pricing database with:
- 100+ EC2 instance types from t2.nano to p4d.24xlarge
- 30+ RDS instance types across db.t3, db.m5, db.m6i, db.r5, db.r6g families
- ElastiCache, OpenSearch, Redshift, SageMaker instance pricing
- Storage pricing for gp2, gp3, io1, io2, st1, sc1 EBS volumes
- Network pricing for NAT Gateway, ALB, NLB, CloudFront, VPN
- Serverless pricing models for Lambda, S3, DynamoDB, Glue, SNS, SQS
All prices are us-east-1 on-demand rates. Reserved instance, Savings Plans, and Spot pricing are not included — actual costs will be lower if you use those.
Create a small EC2 instance or S3 bucket and watch Slack. Note there is a 5–15 minute delay due to CloudTrail event delivery time.
In Lambda console go to Test → Create new test event and use this payload:
{
"version": "0",
"source": "aws.ec2",
"account": "123456789012",
"region": "us-east-1",
"detail-type": "AWS API Call via CloudTrail",
"detail": {
"eventName": "RunInstances",
"eventSource": "ec2.amazonaws.com",
"awsRegion": "us-east-1",
"recipientAccountId": "123456789012",
"eventTime": "2026-03-20T10:30:00Z",
"userIdentity": {
"type": "IAMUser",
"arn": "arn:aws:iam::123456789012:user/your-name",
"userName": "your-name"
},
"requestParameters": {
"instanceType": "m5.4xlarge",
"instancesSet": { "maxCount": 3 }
},
"responseElements": {
"instancesSet": {
"items": [{ "instanceId": "i-0test123456789abc" }]
}
}
}
}aws logs tail /aws/lambda/finops-cost-alert --followAWS bills arrive at the end of the month. By then it is too late — someone spun up a p3.8xlarge GPU instance for a test two weeks ago and forgot to terminate it. That is $12,240/month running silently.
- Developer spins up an oversized RDS instance for testing → immediate alert
- CI/CD pipeline accidentally creates a NAT Gateway in every deploy → caught instantly
- New team member creates resources without tagging → flagged with their identity
- Root account used to create resources →
⚠️ warning sent immediately - Accidental Multi-AZ RDS in dev environment → double cost detected
- Real-time visibility — know about costs as they happen, not at month end
- Accountability — every resource is tied to the person who created it
- Tagging enforcement — the alert reminds teams to tag resources
- Budget awareness — teams see the cost impact of their decisions immediately
Pull requests welcome. When adding a new service:
- Add the hourly/monthly price to the pricing database at the top
- Add an estimator function following the pattern of existing ones
- Register the event name in
EVENT_MAP - Add a test event JSON in
tests/test_events/
Adnan — FinOps and Cloud Infrastructure Engineer
- GitHub: @iam-adnan
Prices are based on AWS on-demand rates for us-east-1 as of March 2026. Always verify current pricing at aws.amazon.com/pricing.