This val summarizes recent Attio events on Slack.
It receives webhooks from Attio, stores them in a SQLite database, and periodically sends AI-generated summaries of recent activity to Slack. The system uses OpenAI to transform raw webhook data into human-readable summaries, helping teams stay informed about CRM activity without being overwhelmed by individual notifications.
- Remix this val
- Get a Slack webhook
& set it as
SLACK_WEBHOOK_URL
in this val's Environment variables in the left sidebar - Get an Attio Access Token
(with all read & write permissions) & set it as
ATTIO_API_KEY
in this val's Environment variables in the left sidebar - Get the name of your Attio workspace and set it as
ATTIO_WORKSPACE_NAME
in this val's Environment variables in the left sidebar - Set
SALES_LIST_ID
to the ID of your Sales List in this val's Environment variables in the left sidebar - Go to
setup.ts
and click run to set up the events database and Attio webhook - Go trigger some Attio events and see the message in Slack!
attio-slack-summaries/
├── webhook.ts # Main HTTP endpoint - receives Attio webhooks via Hono framework
├── config.ts # Configuration - workspace name & list IDs for different CRM lists
├── README.md
├── services/ # Core business logic modules
│ ├── alert/
│ │ └── alert.ts # Cron job - processes recent events & sends Slack summaries
│ ├── attio/
│ │ ├── webhook.ts # Attio webhook management - creates/stores webhook configs
│ │ └── utils.ts # Attio API utilities & helpers
│ ├── auth.ts # HMAC signature verification for webhook security
│ └── webhook/
│ └── handle-webhook.ts # Webhook event processing logic
├── shared/ # 🔄 Reusable utility functions
│ ├── slack.ts # 💬 Slack integration - message formatting & webhook sending
│ ├── format.ts # 📝 Message formatting utilities for human-readable text
│ ├── enrich-entry.ts # 📈 Data enrichment for CRM entries
│ └── initialize-entries.ts # 🆕 Entry initialization logic
├── types/
│ ├── index.ts # Core application types
│ └── webhook.ts # Attio webhook payload type definitions
├── database/
│ ├── schema.ts # SQLite table definitions (events, messages, entries)
│ └── sqlite.ts # Database operations - CRUD for webhook events & messages
└── scripts/
├── setup.ts # One-time setup - creates DB tables & Attio webhook
└── add-list.ts # Add new Attio list to tracking
Attio webhooks return thin events, which contain a bunch of IDs, but no human-readable data, such as:
{ "event_type": "record.merged", "id": { "workspace_id": "b6c564a6-2cf7-49ab-9320-dea013196bd7", "object_id": "a87cf74e-5ca1-4a8d-b8d3-fcca5413d4c3", "record_id": "d64ff9f2-d1f1-424c-8be6-e41129e35697" }, "duplicate_object_id": "a87cf74e-5ca1-4a8d-b8d3-fcca5413d4c3", "duplicate_record_id": "112b5c78-1ffe-457a-8366-181b482888b4", "actor": { "type": "system", "id": null } }
The trickiest part of this val was hydrating these events into human readable form, like "Steve commented on OpenAI: they're using Val Town!"
After trying and failing (for hours!) to get LLMs to write this code for me, I eventually bit the bullet and wrote it by hand. I only handled some of the more common event types, and left myself comments of other example events of types I haven't handled yet.
In theory, Attio has a better way to handle this, analgous to Stripe's method to fetch data for thin events, but I wasn't able to find it. If you figure it out, please send me a pull request or let me know at steve@val.town.