This val summarizes recent events on Attio lists on Slack
This project receives Attio webhooks, verifies and processes them, and stores events, messages, and list entry state data in SQLite. A scheduled cron job uses then periodically sends summaries with these stored messages to Slack.
- 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 - Set
config.workspaceName
inconfig.ts
to the name of your Attio workspace - For each list you want to get notifications about, add an object to the
config.lists
array inconfig.ts
. This object must contain alistId
,notificationHeader
, andobjectName
. To find your list's id, navigate to the list and copy the path segment aftercollection
:https://app.attio.com/<workspaceName/collection/<listId>
. ThenotificationHeader
is the header that will appear in slack.objectName
is the singular name of the object that list entries in the target list are a instances of. If you're unsure, navigate to the record for one of the list entries and copy the path segment after your workspace's name:https://app.attio.com/<workspaceName/<objectName>/<record_id>
- Go to
setup.ts
and click run to set up the events database and Attio webhook 7. 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 ├── deno.json # 📦 Deno configuration - dependencies, compiler options, Val Town types ├── biome.json # 🧹 Code formatting/linting configuration ├── README.md # 📖 Documentation - setup instructions & file explanations ├── TODO.md # 📋 Task tracking │ ├── 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/ # 📨 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/ # 📋 TypeScript type definitions │ ├── index.ts # 🔖 Core application types │ └── webhook.ts # 📡 Attio webhook payload type definitions │ ├── database/ # 🗄️ Data persistence layer │ ├── schema.ts # 🏗️ SQLite table definitions (events, messages, entries) │ └── sqlite.ts # 💾 Database operations - CRUD for webhook events & messages │ ├── scripts/ # 🔧 Setup & maintenance scripts │ ├── setup.ts # 🚀 One-time setup - creates DB tables & Attio webhook │ └── add-list.ts # ➕ Add new CRM list configuration │ ├── dev-helpers/ # 🛠️ Development & debugging tools │ ├── scratch.ts # 🧪 Development scratchpad for testing functions │ └── fetchAttioData.ts # 📥 Manual data fetching utilities │ └── .vt/ # 🏠 Val Town specific files .cursor/ # 👆 Cursor IDE configuration .vscode/ # 💻 VS Code configuration node_modules/ # 📦 Dependencies
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.