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
, and object
Get the name of your Attio workspace and set it as ATTIO_WORKSPACE_NAME
in
this val's Environment variables in the left sidebar 5. Set SALES_LIST_ID
to
the ID of your Sales List in this val's Environment variables in the left
sidebar 6. 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.