Automating AWS Cost Reports with a Webex Bot and AWS Lambda
In this blog post, we will walk through the process of setting up a Webex bot that retrieves AWS Cost and Usage Reports (CUR) using AWS…
Automating AWS Cost Reports with a Webex Bot and AWS Lambda
In this blog post, we will walk through the process of setting up a Webex bot that retrieves AWS Cost and Usage Reports (CUR) using AWS Lambda. This setup enables you to get cost breakdowns across AWS accounts via a simple Webex chat command.

Overview
The solution consists of three key steps:
- Create a Webex bot and add it to a Webex space.
- Deploy an AWS Lambda function with an API Gateway trigger.
- Set up a Webex webhook to send and receive messages.
Step 1: Create a Webex Bot and Add It to a Space
- Navigate to the Webex Developer Portal.
- Sign in and go to My Apps > Create a Bot.
- Provide a bot name, username, and icon, then generate the bot token.
- Copy and save the bot token securely.
- Add the bot to a Webex space where it will respond to cost inquiries.



Step 2: Deploy AWS Lambda with API Gateway
Create a Lambda Function
- Open the AWS Lambda console and create a new function.
- Select Author from scratch, provide a function name, and choose Python as the runtime.
- Assign necessary permissions for accessing AWS Cost Explorer and Organizations API.
Deploy the Code
Use the following Python script for your Lambda function:
import json
import boto3
import datetime
import urllib3
# Webex API & Bot Token
WEBEX_BOT_TOKEN = "your_bot_token_here"
WEBEX_API_URL = "https://webexapis.com/v1"
http = urllib3.PoolManager()
# Get Webex Bot's Person ID
def get_bot_person_id():
url = f"{WEBEX_API_URL}/people/me"
headers = {"Authorization": f"Bearer {WEBEX_BOT_TOKEN}"}
response = http.request("GET", url, headers=headers)
if response.status == 200:
return json.loads(response.data.decode("utf-8")).get("id")
return None
BOT_PERSON_ID = get_bot_person_id()
# Get current month's date range
def get_current_month():
today = datetime.date.today()
start_of_month = today.replace(day=1)
return start_of_month.strftime('%Y-%m-%d'), today.strftime('%Y-%m-%d')
# Get all AWS accounts
def get_all_accounts():
org_client = boto3.client('organizations')
accounts = []
paginator = org_client.get_paginator('list_accounts')
for page in paginator.paginate():
for account in page['Accounts']:
if account['Status'] == 'ACTIVE':
accounts.append({'Id': account['Id'], 'Name': account['Name']})
return accounts
# Get AWS cost for an account
def get_total_cost(account_id, start_date, end_date):
ce_client = boto3.client('ce')
response = ce_client.get_cost_and_usage(
TimePeriod={'Start': start_date, 'End': end_date},
Granularity='MONTHLY',
Metrics=['UnblendedCost'],
Filter={'Dimensions': {'Key': 'LINKED_ACCOUNT', 'Values': [account_id]}}
)
return float(response.get('ResultsByTime', [])[0].get('Total', {}).get('UnblendedCost', {}).get('Amount', 0))
# Generate cost report in Markdown format
def generate_markdown(accounts_sorted):
markdown = "### AWS Cost Report by Account\n\n```
"
markdown += "| {:<50} | {:>10} |\n".format("Account Name (ID)", "Cost (USD)")
markdown += "|" + "-"*52 + "|" + "-"*12 + "|\n"
for account, total_cost in accounts_sorted:
account_str = f"{account['Name']} ({account['Id']})"
if len(account_str) > 50:
account_str = account_str[:47] + "..."
account_str = account_str.ljust(50)
cost_str = f"${total_cost:,.2f}".rjust(10)
markdown += f"| {account_str} | {cost_str} |\n"
markdown += "```
"
return markdown
# Send cost report to Webex
def send_to_webex(report, room_id):
url = f"{WEBEX_API_URL}/messages"
headers = {
"Authorization": f"Bearer {WEBEX_BOT_TOKEN}",
"Content-Type": "application/json"
}
max_length = 7000 # Keeping below Webex limit
report_chunks = [report[i:i+max_length] for i in range(0, len(report), max_length)]
for chunk in report_chunks:
payload = json.dumps({"roomId": room_id, "markdown": chunk})
response = http.request("POST", url, body=payload, headers=headers)
return response.status
# Main Lambda function
def lambda_handler(event, context):
body = json.loads(event['body'])
if 'data' in body:
message_id = body['data']['id']
room_id = body['data']['roomId']
sender_id = body['data']['personId']
if sender_id == BOT_PERSON_ID:
return {"statusCode": 200, "body": "Ignored bot message"}
message_details = fetch_message_details(message_id)
user_message = message_details.get("text", "").lower()
if "cost report" in user_message:
start_date, end_date = get_current_month()
accounts = get_all_accounts()
account_costs = [(account, get_total_cost(account['Id'], start_date, end_date)) for account in accounts]
account_costs.sort(key=lambda x: x[1], reverse=True)
markdown_report = generate_markdown(account_costs)
send_to_webex(markdown_report, room_id)
return {"statusCode": 200, "body": "Processed"}
Create an API Gateway Trigger
- Navigate to API Gateway and create a new REST API.
- Set up a POST method for the resource and link it to the Lambda function.
- Deploy the API and copy the invoke URL.
Step 3: Add Webex Webhook
Run the following command to register a webhook that forwards messages to the Lambda function:
curl -X POST "https://webexapis.com/v1/webhooks" \
-H "Authorization: Bearer YOUR_BOT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Cost Report",
"targetUrl": "YOUR_API_GATEWAY_URL",
"resource": "messages",
"event": "created"
}'
Once set up, your Webex bot will respond with AWS cost reports when prompted in a chat.

As an alternative approach to this, you can also schedule the AWS cost report using Amazon EventBridge to trigger the Lambda function at a set interval, such as daily or monthly. Instead of relying on Webex message triggers, the Lambda function can fetch cost data automatically and send the report using a Webex Incoming Webhook.
메타데이터
- post_id
- ff424e7b8c79
- slug
- automating-aws-cost-reports-with-a-webex-bot-and-aws-lambda-ff424e7b8c79
- url
- https://medium.com/@aadhith/automating-aws-cost-reports-with-a-webex-bot-and-aws-lambda-ff424e7b8c79
- canonical_url
- https://medium.com/@aadhith/automating-aws-cost-reports-with-a-webex-bot-and-aws-lambda-ff424e7b8c79
- author_url
- https://medium.com/@aadhith
- status
- ok
- fetched_at
- 2026-07-20 20:15:43