• Townie
    AI
  • Blog
  • Docs
  • Pricing
Log inSign up
nbbaier

nbbaier

attio-slack-summaries

Remix of stevekrouse/attio-slack-summaries
Public
Like
attio-slack-summaries
Home
Code
9
core
5
scripts
2
.vtignore
README.md
C
alert.ts
formatters.ts
slack.ts
types.ts
H
webhook.ts
Branches
4
Pull requests
Remixes
1
History
Environment variables
2
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Sign up now
Code
/
README.md
Code
/
README.md
Search
9/9/2025
Viewing readonly version of simplification branch: v467
View latest version
README.md

Attio Slack Summaries

Set up

  1. Remix this val
  2. Get a Slack webhook & set it as SLACK_WEBHOOK_URL in this val's Environment variables in the left sidebar
  3. 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
  4. Configure your lists in webhook.ts:
    • Update the listIds array with your Attio list IDs
    • To find your list's id, navigate to the list and copy the path segment after collection: https://app.attio.com/<workspaceName>/collection/<listId>
  5. Run setup.ts to set up the database and Attio webhook
  6. Run bootstrap.ts to initialize the database with the current state of your configured lists.
  7. Go trigger some Attio events and see the message in Slack! (If you want them faster, you can run alert.ts manually.)

Customization & Configuration

Adding Lists

To track additional Attio lists, add their IDs to the listIds array in webhook.ts:

export const listIds = [ "your-first-list-id", "your-second-list-id", // Add more list IDs here ];

After adding new lists, run bootstrap.ts to initialize them in the database.

Customizing Attribute Formatters

The formatters.ts file controls how Attio attribute values appear in Slack messages. It's organized into sections for easy customization:

Text & Selections:

const textFormatters = { text: (av: AttioTextValue) => av.value, status: (av: AttioStatusValue) => av.status.title, select: (av: AttioSelectValue) => av.option.title, checkbox: (av: AttioCheckboxValue) => av.value.toString(), };

Numbers & Currency:

const numberFormatters = { currency: (av: AttioCurrencyValue) => { return new Intl.NumberFormat("en-US", { style: "currency", currency: av.currency_code, }).format(Number(av.currency_value)); }, number: (av: AttioNumberValue) => av.value.toString(), };

Dates, People, Links, etc. - Each category has its own section with examples showing input → output.

Customizing Slack Messages

The slack.ts file controls Slack message appearance:

Message Settings:

const SLACK_CONFIG = { maxCommentLength: 150, // Comment truncation groupByEntry: true, // Group changes by record allowEmojis: true, // Enable emoji in headers };

Message Templates:

const MESSAGE_TEMPLATES = { entryCreated: (actor: string, name: string) => `${actor} added ${name}`, entryDeleted: (actor: string, name: string) => `${actor} removed ${name}`, fieldChanged: (field: string, oldValue: string, newValue: string) => `*${field}*: ${oldValue} → ${newValue}`, // ... more templates };

Link Formats:

const LINK_FORMATS = { entry: ( workspaceSlug: string, objectSlug: string, recordId: string, name: string ) => `<https://app.attio.com/${workspaceSlug}/${objectSlug}/${recordId}/overview|${name}>`, // ... more link formats };

Project Structure

attio-slack-summaries/
├── webhook.ts                   # Main HTTP endpoint - receives Attio webhooks
├── alert.ts                     # Cron job - processes events & sends Slack messages
├── types.ts                     # Core type definitions
├── formatters.ts                # Attribute value formatters (user-customizable)
├── slack.ts                     # Slack integration (user-customizable)
├── core/                        # Internal system logic
│   ├── alert-processor.ts       # Event processing & message creation
│   ├── api-client.ts            # Attio API client
│   ├── auth.ts                  # Webhook authentication & setup
│   ├── database.ts              # SQLite operations
│   └── webhook-handler.ts       # Event routing & state management
└── scripts/                     # Setup & maintenance scripts
    ├── setup.ts                 # Creates database & Attio webhook
    └── bootstrap.ts             # Seeds initial state data

Template Files (designed for user customization):

  • formatters.ts - How attribute values display
  • slack.ts - Message appearance & templates
  • webhook.ts - List configuration

Core Files (internal system logic):

  • core/ directory - Processing, API, database operations
  • types.ts - Type definitions for the entire system

Setup Files:

  • scripts/ directory - One-time setup and maintenance

How it works

The system uses Attio webhooks to receive real-time notifications about changes to your lists. Each webhook event triggers:

  1. Event Reception (webhook.ts) - Validates and filters incoming events
  2. State Storage (webhook-handler.ts) - Stores current state in SQLite
  3. Change Detection (alert-processor.ts) - Compares states to detect changes
  4. Message Generation (formatters.ts) - Formats changes into human readable strings
  5. Slack Delivery (slack.ts) - Sends formatted messages to Slack

Attio webhooks return very thin events. ontain only IDs, not human-readable data or change details. In order to send human readable messages to Slack, we need do a lot of work to enrich the events with data fro the Attio API.

The hardest part of this project was transforming these events into data that could be used to track the state of list entries and comments. I've accomplished this here by getting the state of the entry/comment when a webhook event comes in, storing that state in the database. We go from a webhook that looks like this:

"<webhook>"

To a state object that looks like this:

// Stored state includes complete API response const stateData = { id: "entry-123", type: "entry", action: "updated", timestamp: Date.now(), rawData: entry, // Complete Attio API response metadata: { recordId, objectId, actor }, };

Messages are generated by comparing consecutive states to detect changes. Diffing and message generation is done at run-time when the cron job triggers. This cuts down on the complexity of the database schema (differences and messages are not stored).

NOTE: Currently, this system only tracks the state of the state of list entries and comments on those list entries. The webhook is setup to only accept events related to list entries and comments (see CODE LINK), and the logic of generating messages is not set up to handle states outside those two things. In fact, the database is not set up to store data related to things other without both a list id and an entry id.

This is especially relevant for comments. Attio comments can be associated with a record (an instance of an object type: PayPal is a record of the object type Company) OR a list entry (which is an instance of a record on a specific list, PayPal on a sales list). When a comment is added to a record, it is associated with the record, not the list entry. When a comment is added to a list entry, it is associated with the list entry, not the record. You will not see notifications for comments on records.

The logic does not handle, for example, comments being added to records in Attio that are associated with the

Template-Friendly Design

The codebase is organized for easy customization:

  • User-facing files (formatters.ts, slack.ts) contain clear sections with examples
  • Internal logic is isolated in core/ directory
  • Type safety ensures custom formatters match Attio attribute types
  • Minimal configuration - just list IDs and environment variables

Performance Optimizations

  • Early filtering - Only processes events for configured lists
  • Caching - API responses cached to reduce duplicate requests
  • Watermarking - Only processes new events since last run
  • Head state tracking - Maintains previous state for accurate diffing

Error Handling

  • Graceful degradation - System continues processing if individual events fail
  • Detailed logging - All operations logged for debugging
  • Webhook validation - HMAC signature verification prevents unauthorized access
  • Type safety - TypeScript prevents many run-time errors

How Attio Webhooks Work

Attio webhooks deliver minimal event data containing mostly IDs:

{ "event_type": "list-entry.updated", "id": { "workspace_id": "...", "list_id": "...", "entry_id": "..." }, "parent_record_id": "...", "actor": { "type": "workspace-member", "id": "..." } }

The system enriches this data by:

  1. Fetching complete entry data from Attio API
  2. Storing enriched state in SQLite
  3. Comparing with previous states to detect changes
  4. Formatting changes into readable Slack messages

This approach transforms minimal webhook events into detailed, actionable Slack notifications.

FeaturesVersion controlCode intelligenceCLI
Use cases
TeamsAI agentsSlackGTM
ExploreDocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareersBrandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Terms of usePrivacy policyAbuse contact
© 2025 Val Town, Inc.