|
|
||
|---|---|---|
| .github | ||
| src | ||
| .gitignore | ||
| README.md | ||
| samconfig.toml.example | ||
| template.yaml | ||
Expense Approval Bot
Archived. This repo was merged into payments-dashboard on 2026-05-12 (PR #28). Handlers were rewritten from Python to JavaScript ESM. The new endpoint is
POST /slack/expense-eventson the payments-dashboard API Gateway. The standalone CloudFormation stack, secrets, and OIDC deploy role have been deleted.
Slack reaction-driven expense approval router. Replaces the Pipedream expenses_pipeline workflow.
Flow
A user reacts ✅ to a message in an expense channel. The bot:
- Maps the source channel to the next stage's channel.
- Copies the message text (stripped of any prior "react to advance" hint) into the next channel. When advancing out of Submitted, the copy also gets a permalink back to the user's original message.
- Depending on where we're advancing from:
- From Submitted: keeps the user's original message in place and posts a threaded
➡️ Advanced to {Stage}reply. The Submitted channel is the permanent audit trail. - From Processed or Authorized: deletes the old copy so each intermediate channel stays a clean "current work" queue.
- From Submitted: keeps the user's original message in place and posts a threaded
Channel chain: Submitted → Processed → Authorized → Matched. The bot can only delete its own posts, which is why the Submitted original (a real user message) is preserved by design.
Architecture
- Receiver Lambda (
src/receiver/) — fronted by API Gateway HTTP API. Verifies the Slack request signature, handles the URL-verification handshake, and async-invokes the processor. Returns 200 within Slack's 3-second window to prevent retries. - Processor Lambda (
src/processor/) — does the 5-step sequence of Slack API calls. No inbound HTTP; invoked only by the receiver. - Secrets — Slack bot token and signing secret each stored as separate AWS Secrets Manager secrets. Each Lambda only has IAM access to the secret it needs.
Runtime: Python 3.12 on arm64, no third-party dependencies (stdlib urllib for HTTP, boto3 from the Lambda runtime).
Setup
-
Store the two Slack credentials in Secrets Manager:
aws secretsmanager create-secret \ --name expense-bot-slack-token \ --secret-string "xoxb-..." aws secretsmanager create-secret \ --name expense-bot-slack-signing-secret \ --secret-string "..." -
Copy the sample SAM config and fill in real values:
cp samconfig.toml.example samconfig.tomlSlackBotTokenSecretArn— ARN of the bot-token secretSlackSigningSecretArn— ARN of the signing-secret secret
-
Build and deploy:
sam build && sam deploy -
After the first deploy, take the
SlackEventsUrloutput and paste it into the Slack app's Event Subscriptions → Request URL. Subscribe the bot to thereaction_addedevent.
Configuration
Channel IDs live in src/processor/app.py:
SUBMITTED_CHANNEL— the user-authored origin channel. Controls both the "include a permalink" behavior and the "preserve vs delete source" branch.STAGES— the full chain mapping each source channel to itsnextchannel and human-readablelabel.
If the Submitted channel changes, update SUBMITTED_CHANNEL and the first key of STAGES. For any other stage change, update STAGES only.
Manual testing
Invoke the processor directly with a synthetic Slack reaction event:
aws lambda invoke \
--function-name expense-approval-processor \
--payload '{"reaction":"white_check_mark","item":{"channel":"C0AQ2AWLNEN","ts":"1700000000.000000"}}' \
/dev/stdout