Skip to content

Slack alerts via AWS Chatbot

CloudWatch alarms publish to the foundation alerts SNS topic (sns_alerts_topic_arn). This wires that topic to a Slack channel through AWS Chatbot (Amazon Q Developer in chat applications), so alarms render as formatted cards in Slack.

The Pulumi wiring lives in infra/foundation/modules/slack_chatbot.py. Stacks where slack_alerts_required is false are inert until two ids are set. The dev stack sets slack_alerts_required: true, so after this PR lands a missing id fails the deploy instead of silently removing an existing Slack alert path.

The Slack channel configuration is explicitly created in eu-west-1, because the AWS Chatbot control plane is not available in Frankfurt. It still subscribes the eu-central-1 alerts SNS topic.

What reaches Slack

Every CloudWatch alarm defined across the infra Pulumi stacks lists the alerts topic in its alarm_actions (and ok_actions), so it forwards to Slack. The alarms live in three modules — all pointed at the same foundation alerts topic:

Stack Module Alarms
foundation modules/cognito_alarms.py dev-client sign-in throttling (non-prod only)
foundation modules/secret_rotation.py RDS master-secret rotation failure
edge modules/cloudwatch_alarms.py per-service health + latency, stale/kill-switch trading heartbeats, active-strategies, per-Lambda errors, DLQ depth, infra drift, promotion-audit write failure, RDS CPU + free storage, API Gateway 5xx rate, Step Functions failures, and the system-health composite alarm

The edge alarms reach the topic via a StackReference to foundation (foundation.get_output("sns_alerts_topic_arn")), so there is a single Slack destination for the whole platform.

Two guard tests keep this invariant from silently regressing when an alarm is added: TestAllAlarmsPublishToAlertsTopic in infra/edge/tests/test_modules.py builds the full CloudWatchAlarms set and asserts each alarm carries the alerts-topic action, and the foundation alarm tests (test_cognito_alarms.py, test_secret_rotation.py) assert the same.

One deliberate exception: the active-strategies alarm sets actions_enabled=False in any environment where min_strategies == 0 — the default in every environment today, dev and prod alike. It still lists the topic but is muted, because with a threshold of 0 it can never fire. To arm it, set the key on the edge stack, not foundation:

cd infra/edge
pulumi config set alarm_min_strategies 1 --stack <env>

One-time setup

1. Authorize the AWS Slack app (console only — cannot be done in IaC)

AWS Console → Amazon Q Developer in chat applicationsConfigure new clientSlackAllow for the target workspace.

This authorizes the AWS app in the Slack workspace and reveals the workspace (team) id (T........) on the client's detail page.

2. Get the channel id

In Slack: right-click the target channel → View channel details → the id (C........) is at the bottom. Invite the app to a private channel with /invite @aws.

3. Confirm the dev ids in stack config (ids only — not secrets)

For the dev foundation stack, the committed stack config is the source of truth. infra/foundation/Pulumi.dev.yaml stores slack_team_id, slack_channel_id, and slack_alerts_required: true, so local and CI applies use the same durable values. The ids are Slack/AWS identifiers, not secrets.

cd infra/foundation
pulumi config get tradai-foundation:slack_team_id --stack dev
pulumi config get tradai-foundation:slack_channel_id --stack dev

If either id is removed or mistyped, the committed required guard fails the Pulumi program before it can remove already-created Chatbot resources. Non-dev stacks do not inherit the dev channel; configure their stack values directly so each environment can use its own Slack channel.

4. Apply

Trigger deploy-infra.yml manually with workflow_dispatch (layer=foundation, stack=dev, command=up), or run just infra-up-foundation dev locally. A merge to dev also deploys foundation, but only when the merge touches infra/**; config-only changes outside the repo do not start this path.

Chatbot creates the channel configuration bound to the alerts topic. No SNS subscription confirmation is needed (Chatbot manages it), unlike email.

The alerts SNS topic remains in eu-central-1. The Chatbot channel configuration itself is explicitly pinned to eu-west-1, the nearest AWS-supported Chatbot control-plane region, so the provider cannot silently fall back to us-west-2.

Verify

Trigger a test notification from the SNS topic and confirm it lands in the channel:

AWS_PROFILE=tradai aws sns publish --region eu-central-1 \
  --topic-arn "$(cd infra/foundation && pulumi stack output sns_alerts_topic_arn --stack dev)" \
  --subject "chatbot-smoke-test" \
  --message '{
    "version": "1.0",
    "source": "custom",
    "content": {
      "description": "Slack alerts smoke test from the TradAI alerts SNS topic."
    }
  }'

Security notes

  • The channel's assumed IAM role trusts only chatbot.amazonaws.com and has a local notification-only policy with cloudwatch:Describe*, cloudwatch:Get*, and cloudwatch:List* (so cards can show alarm state/graphs).
  • A ReadOnlyAccess guardrail policy independently caps what anyone could invoke from the channel — the bot is a notification path, not a mutation one.

Scope

This PR wires dev through committed stack config. Set the same two config keys directly on the prod foundation stack to enable it there (likely a different channel).