| name | attio-webhooks-events |
| description | Implement Attio v2 webhooks -- subscribe to record/list/note/task events,
verify signatures, filter by object or attribute, and handle idempotently.
Trigger: "attio webhook", "attio events", "attio webhook signature",
"handle attio events", "attio notifications", "attio real-time".
|
| allowed-tools | Read, Write, Edit, Bash(curl:*) |
| version | 1.0.0 |
| license | MIT |
| author | Jeremy Longshore <jeremy@intentsolutions.io> |
| tags | ["saas","crm","attio"] |
| compatible-with | claude-code |
Attio Webhooks & Events
Overview
Attio v2 webhooks deliver real-time event notifications to your HTTPS endpoint. You can subscribe to specific event types and filter by object, list, or attribute to reduce volume. Webhooks are managed via POST /v2/webhooks and verified with HMAC-SHA256 signatures.
Prerequisites
- HTTPS endpoint accessible from the internet
- Scopes:
webhook:read-write
ATTIO_WEBHOOK_SECRET stored securely
Attio Webhook Event Types
| Category | Event types |
|---|
| Record | record.created, record.updated, record.deleted, record.merged |
| List Entry | list-entry.created, list-entry.updated, list-entry.deleted |
| Note | note.created, note.updated, note.deleted |
| Task | task.created, task.updated, task.deleted |
| Comment | comment.created, comment.updated, comment.deleted |
| List | list.created, list.updated, list.deleted |
| Object Attribute | object-attribute.created, object-attribute.updated |
| List Attribute | list-attribute.created, list-attribute.updated |
| Workspace Member | workspace-member.created, workspace-member.updated |
| Call Recording | call-recording.created, call-recording.updated |
Instructions
Step 1: Create a Webhook Subscription
const webhook = await client.post<{
data: {
id: { workspace_id: string; webhook_id: string };
target_url: string;
subscriptions: Array<{ event_type: string; filter?: object }>;
created_at: string;
};
}>("/webhooks", {
target_url: "https://yourapp.com/api/webhooks/attio",
subscriptions: [
{ event_type: "record.created" },
{ event_type: "record.updated" },
{ event_type: "record.deleted" },
{
event_type: "list-entry.created",
filter: { list: { $eq: "sales_pipeline" } },
},
{
event_type: "record.updated",
filter: {
$and: [
{ object: { $eq: "deals" } },
{ attribute: { $eq: "stage" } },
],
},
},
{ : },
{ : },
],
});
.(, webhook...);
Step 2: List and Manage Webhooks
const webhooks = await client.get<{
data: Array<{
id: { webhook_id: string };
target_url: string;
subscriptions: Array<{ event_type: string }>;
}>;
}>("/webhooks");
const wh = await client.get(`/webhooks/${webhookId}`);
await client.patch(`/webhooks/${webhookId}`, {
subscriptions: [
{ event_type: "record.created" },
{ event_type: "record.updated" },
],
});
await client.delete(`/webhooks/${webhookId}`);
Step 3: Webhook Endpoint with Signature Verification
import express from "express";
import crypto from "crypto";
const app = express();
app.post(
"/api/webhooks/attio",
express.raw({ type: "application/json" }),
async (req, res) => {
const signature = req.headers["x-attio-signature"] as string;
const timestamp = req.headers["x-attio-timestamp"] as string;
if (!verifyAttioWebhook(req.body, signature, timestamp)) {
console.error("Webhook signature verification failed");
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
res.status(200).json({ : });
{
(event);
} (err) {
.(, event., err);
}
}
);
(): {
secret = process..!;
age = .() - (timestamp) * ;
(age > ) {
.(, age, );
;
}
payload = ;
expected = crypto
.(, secret)
.(payload)
.();
{
crypto.(
.(signature),
.(expected)
);
} {
;
}
}
Step 4: Event Handler with Type Routing
interface AttioWebhookEvent {
event_type: string;
id: { event_id: string };
created_at: string;
actor: { type: string; id: string };
object?: { id: { object_id: string }; api_slug: string };
record?: { id: { record_id: string } };
list?: { id: { list_id: string }; api_slug: string };
entry?: { id: { entry_id: string } };
}
type EventHandler = (event: AttioWebhookEvent) => Promise<void>;
const handlers: Record<string, EventHandler> = {
"record.created": async (event) => {
const objectSlug = event.object?.api_slug;
const recordId = event.?.?.;
.();
(objectSlug === ) {
person = client.(
);
(person);
}
},
: (event) => {
.();
},
: (event) => {
.();
},
: (event) => {
.();
},
: (event) => {
.();
},
: (event) => {
.();
},
: (event) => {
.();
},
};
(): <> {
handler = handlers[event.];
(!handler) {
.();
;
}
(event);
}
Step 5: Idempotent Event Processing
const processedEvents = new Set<string>();
async function processEventIdempotently(event: AttioWebhookEvent): Promise<void> {
const eventId = event.id.event_id;
if (processedEvents.has(eventId)) {
console.log(`Duplicate event skipped: ${eventId}`);
return;
}
await processEvent(event);
processedEvents.add(eventId);
if (processedEvents.size > 10_000) {
const entries = Array.from(processedEvents);
entries.slice(0, entries.length - 10_000).forEach((id) => processedEvents.delete(id));
}
}
Step 6: Local Webhook Testing
ngrok http 3000
curl -X POST https://api.attio.com/v2/webhooks \
-H "Authorization: Bearer ${ATTIO_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"target_url": "https://abc123.ngrok.io/api/webhooks/attio",
"subscriptions": [{"event_type": "record.created"}]
}'
curl -X POST https://api.attio.com/v2/objects/people/records \
-H "Authorization: Bearer ${ATTIO_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"data": {
"values": {
"email_addresses": ["webhook-test@example.com"],
"name": [{"first_name": "Test", "last_name": "Webhook"}]
}
}
}'
Error Handling
| Issue | Cause | Solution |
|---|
| Signature mismatch | Wrong secret or body parsed before verification | Use express.raw(), verify raw body |
| Duplicate events | No idempotency | Track event IDs in Redis/DB |
| Missed events | Handler returns non-200 | Return 200 immediately, process async |
| Too many events | No filtering | Add filters to webhook subscriptions |
| Webhook deleted | Attio cleanup or token revoked | Re-register webhook, monitor with health check |
Resources
Next Steps
For performance optimization, see attio-performance-tuning.