attio-webhooks-events

'Implement Attio v2 webhooks -- subscribe to record/list/note/task events,

4 Tools
attio-pack Plugin
saas packs Category

Allowed Tools

ReadWriteEditBash(curl:*)

Provided by Plugin

attio-pack

Claude Code skill pack for Attio (18 skills)

saas packs v1.0.0
View Plugin

Installation

This skill is included in the attio-pack plugin:

/plugin install attio-pack@claude-code-plugins-plus

Click to copy

Instructions

Attio Webhooks & Events

Overview

Attio v2 webhooks deliver real-time CRM event notifications to your HTTPS endpoint. Subscribe to record, list-entry, note, and task events with optional object or attribute filters to reduce volume. Webhooks are managed via POST /v2/webhooks and verified with HMAC-SHA256 signatures using a timestamp-prefixed payload.

Webhook Registration


const webhook = await fetch("https://api.attio.com/v2/webhooks", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.ATTIO_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    target_url: "https://yourapp.com/webhooks/attio",
    subscriptions: [
      { event_type: "record.created" },
      { event_type: "record.updated", filter: { object: { $eq: "deals" } } },
      { event_type: "note.created" },
      { event_type: "task.completed" },
    ],
  }),
});

Signature Verification


import crypto from "crypto";
import { Request, Response, NextFunction } from "express";

function verifyAttioSignature(req: Request, res: Response, next: NextFunction) {
  const signature = req.headers["x-attio-signature"] as string;
  const timestamp = req.headers["x-attio-timestamp"] as string;
  const age = Date.now() - parseInt(timestamp) * 1000;
  if (age > 300_000) return res.status(401).json({ error: "Timestamp too old" });
  const payload = `${timestamp}.${req.body.toString()}`;
  const expected = crypto.createHmac("sha256", process.env.ATTIO_WEBHOOK_SECRET!)
    .update(payload).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).json({ error: "Invalid signature" });
  }
  next();
}

Event Handler


import express from "express";
const app = express();

app.post("/webhooks/attio", express.raw({ type: "application/json" }), verifyAttioSignature, (req, res) => {
  const event = JSON.parse(req.body.toString());
  res.status(200).json({ received: true });

  switch (event.event_type) {
    case "record.created":
      syncRecordToCRM(event.object?.api_slug, event.record?.id?.record_id); break;
    case "record.updated":
      reindexRecord(event.object?.api_slug, event.record?.id?.record_id); break;
    case "note.created":
      forwardToNotionSync(event.id.event_id); break;
    case "task.completed":
      closeProjectTask(event.id.event_id); break;
  }
});

Event Types

Event Payload Fields Use Case
record.created object.apislug, record.recordid, actor Sync new contacts/deals to external CRM
record.updated object.apislug, record.recordid, attribute Re-index changed records
note.created event_id, actor, record Forward meeting notes to Notion
task.completed event_id, actor, record Close linked project management tasks
list-entry.created list.apislug, entry.entryid Trigger pipeline stage automation

Retry & Idempotency


const processed = new Set<string>();

async function handleIdempotent(event: { id: { event_id: string }; event_type: string }) {
  const eventId = event.id.event_id;
  if (processed.has(eventId)) return;
  await routeEvent(event);
  processed.add(eventId);
  if (processed.size > 10_000) {
    const entries = Array.from(processed);
    entries.slice(0, entries.length - 10_000).forEach((id) => processed.delete(id));
  }
}

Error Handling

Issue Cause Fix
Signature mismatch Body parsed before raw verification Use express.raw(), verify raw body
Duplicate events Attio retry on timeout Track event_id in Redis or DB
Missed events Handler returns non-200 Return 200 immediately, process async
Too many events No subscription filtering Add filter clauses to subscriptions

Resources

Next Steps

See attio-security-basics.

Ready to use attio-pack?