Skip to content

Commit bd0a30e

Browse files
committed
fixed and tested attio webhook lifecycle
1 parent e8cd0a3 commit bd0a30e

File tree

4 files changed

+207
-4
lines changed

4 files changed

+207
-4
lines changed

apps/sim/lib/webhooks/processor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -979,16 +979,18 @@ export async function queueWebhookExecution(
979979
const triggerId = providerConfig.triggerId as string | undefined
980980

981981
if (triggerId && triggerId !== 'attio_webhook') {
982-
const { isAttioPayloadMatch } = await import('@/triggers/attio/utils')
982+
const { isAttioPayloadMatch, getAttioEvent } = await import('@/triggers/attio/utils')
983983
if (!isAttioPayloadMatch(triggerId, body)) {
984-
const eventType = body?.event_type as string | undefined
984+
const event = getAttioEvent(body)
985+
const eventType = event?.event_type as string | undefined
985986
logger.debug(
986987
`[${options.requestId}] Attio event mismatch for trigger ${triggerId}. Event: ${eventType}. Skipping execution.`,
987988
{
988989
webhookId: foundWebhook.id,
989990
workflowId: foundWorkflow.id,
990991
triggerId,
991992
receivedEvent: eventType,
993+
bodyKeys: Object.keys(body),
992994
}
993995
)
994996
return NextResponse.json({ status: 'skipped', reason: 'event_type_mismatch' })

apps/sim/lib/webhooks/provider-subscriptions.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1093,7 +1093,12 @@ export async function createAttioWebhookSubscription(
10931093

10941094
attioLogger.info(
10951095
`[${requestId}] Successfully created webhook in Attio for webhook ${webhookData.id}.`,
1096-
{ attioWebhookId: webhookId }
1096+
{
1097+
attioWebhookId: webhookId,
1098+
targetUrl: notificationUrl,
1099+
subscriptionCount: subscriptions.length,
1100+
status: data.status,
1101+
}
10971102
)
10981103

10991104
return { externalId: webhookId, webhookSecret: secret || '' }

apps/sim/lib/webhooks/utils.server.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1291,6 +1291,49 @@ export async function formatWebhookInput(
12911291
}
12921292
}
12931293

1294+
if (foundWebhook.provider === 'attio') {
1295+
const {
1296+
extractAttioRecordData,
1297+
extractAttioRecordUpdatedData,
1298+
extractAttioRecordMergedData,
1299+
extractAttioNoteData,
1300+
extractAttioTaskData,
1301+
extractAttioCommentData,
1302+
extractAttioListEntryData,
1303+
extractAttioListEntryUpdatedData,
1304+
extractAttioGenericData,
1305+
} = await import('@/triggers/attio/utils')
1306+
1307+
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
1308+
const triggerId = providerConfig.triggerId as string | undefined
1309+
1310+
if (triggerId === 'attio_record_updated') {
1311+
return extractAttioRecordUpdatedData(body)
1312+
}
1313+
if (triggerId === 'attio_record_merged') {
1314+
return extractAttioRecordMergedData(body)
1315+
}
1316+
if (triggerId === 'attio_record_created' || triggerId === 'attio_record_deleted') {
1317+
return extractAttioRecordData(body)
1318+
}
1319+
if (triggerId?.startsWith('attio_note_')) {
1320+
return extractAttioNoteData(body)
1321+
}
1322+
if (triggerId?.startsWith('attio_task_')) {
1323+
return extractAttioTaskData(body)
1324+
}
1325+
if (triggerId?.startsWith('attio_comment_')) {
1326+
return extractAttioCommentData(body)
1327+
}
1328+
if (triggerId === 'attio_list_entry_updated') {
1329+
return extractAttioListEntryUpdatedData(body)
1330+
}
1331+
if (triggerId === 'attio_list_entry_created' || triggerId === 'attio_list_entry_deleted') {
1332+
return extractAttioListEntryData(body)
1333+
}
1334+
return extractAttioGenericData(body)
1335+
}
1336+
12941337
return body
12951338
}
12961339

apps/sim/triggers/attio/utils.ts

Lines changed: 154 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,15 @@ export const TRIGGER_EVENT_MAP: Record<string, string[]> = {
290290
attio_list_entry_deleted: ['list-entry.deleted'],
291291
}
292292

293+
/**
294+
* Extracts the first event from an Attio webhook payload.
295+
* Attio wraps events in an `events` array: `{ webhook_id, events: [{ event_type, id, ... }] }`.
296+
*/
297+
export function getAttioEvent(body: Record<string, unknown>): Record<string, unknown> | undefined {
298+
const events = body.events as Array<Record<string, unknown>> | undefined
299+
return events?.[0]
300+
}
301+
293302
/**
294303
* Checks if an Attio webhook payload matches a trigger.
295304
*/
@@ -298,11 +307,155 @@ export function isAttioPayloadMatch(triggerId: string, body: Record<string, unkn
298307
return true
299308
}
300309

301-
const eventType = body.event_type as string | undefined
310+
const event = getAttioEvent(body)
311+
const eventType = event?.event_type as string | undefined
302312
if (!eventType) {
303313
return false
304314
}
305315

306316
const acceptedEvents = TRIGGER_EVENT_MAP[triggerId]
307317
return acceptedEvents ? acceptedEvents.includes(eventType) : false
308318
}
319+
320+
/**
321+
* Extracts formatted data from an Attio record event payload.
322+
* Used for record.created, record.deleted triggers.
323+
*/
324+
export function extractAttioRecordData(body: Record<string, unknown>): Record<string, unknown> {
325+
const event = getAttioEvent(body) ?? {}
326+
const id = (event.id as Record<string, unknown>) ?? {}
327+
return {
328+
eventType: event.event_type ?? null,
329+
workspaceId: id.workspace_id ?? null,
330+
objectId: id.object_id ?? null,
331+
recordId: id.record_id ?? null,
332+
}
333+
}
334+
335+
/**
336+
* Extracts formatted data from an Attio record.updated event payload.
337+
*/
338+
export function extractAttioRecordUpdatedData(
339+
body: Record<string, unknown>
340+
): Record<string, unknown> {
341+
const event = getAttioEvent(body) ?? {}
342+
const id = (event.id as Record<string, unknown>) ?? {}
343+
return {
344+
eventType: event.event_type ?? null,
345+
workspaceId: id.workspace_id ?? null,
346+
objectId: id.object_id ?? null,
347+
recordId: id.record_id ?? null,
348+
attributeId: id.attribute_id ?? null,
349+
}
350+
}
351+
352+
/**
353+
* Extracts formatted data from an Attio record.merged event payload.
354+
*/
355+
export function extractAttioRecordMergedData(
356+
body: Record<string, unknown>
357+
): Record<string, unknown> {
358+
const event = getAttioEvent(body) ?? {}
359+
const id = (event.id as Record<string, unknown>) ?? {}
360+
return {
361+
eventType: event.event_type ?? null,
362+
workspaceId: id.workspace_id ?? null,
363+
objectId: id.object_id ?? null,
364+
recordId: id.record_id ?? null,
365+
duplicateObjectId: (event.duplicate_object_id as string) ?? null,
366+
duplicateRecordId: (event.duplicate_record_id as string) ?? null,
367+
}
368+
}
369+
370+
/**
371+
* Extracts formatted data from an Attio note event payload.
372+
*/
373+
export function extractAttioNoteData(body: Record<string, unknown>): Record<string, unknown> {
374+
const event = getAttioEvent(body) ?? {}
375+
const id = (event.id as Record<string, unknown>) ?? {}
376+
return {
377+
eventType: event.event_type ?? null,
378+
workspaceId: id.workspace_id ?? null,
379+
noteId: id.note_id ?? null,
380+
parentObjectId: (event.parent_object_id as string) ?? null,
381+
parentRecordId: (event.parent_record_id as string) ?? null,
382+
}
383+
}
384+
385+
/**
386+
* Extracts formatted data from an Attio task event payload.
387+
*/
388+
export function extractAttioTaskData(body: Record<string, unknown>): Record<string, unknown> {
389+
const event = getAttioEvent(body) ?? {}
390+
const id = (event.id as Record<string, unknown>) ?? {}
391+
return {
392+
eventType: event.event_type ?? null,
393+
workspaceId: id.workspace_id ?? null,
394+
taskId: id.task_id ?? null,
395+
}
396+
}
397+
398+
/**
399+
* Extracts formatted data from an Attio comment event payload.
400+
*/
401+
export function extractAttioCommentData(body: Record<string, unknown>): Record<string, unknown> {
402+
const event = getAttioEvent(body) ?? {}
403+
const id = (event.id as Record<string, unknown>) ?? {}
404+
return {
405+
eventType: event.event_type ?? null,
406+
workspaceId: id.workspace_id ?? null,
407+
threadId: (event.thread_id as string) ?? null,
408+
commentId: id.comment_id ?? null,
409+
objectId: (event.object_id as string) ?? null,
410+
recordId: (event.record_id as string) ?? null,
411+
listId: (event.list_id as string) ?? null,
412+
entryId: (event.entry_id as string) ?? null,
413+
}
414+
}
415+
416+
/**
417+
* Extracts formatted data from an Attio list-entry event payload.
418+
* Used for list-entry.created, list-entry.deleted triggers.
419+
*/
420+
export function extractAttioListEntryData(body: Record<string, unknown>): Record<string, unknown> {
421+
const event = getAttioEvent(body) ?? {}
422+
const id = (event.id as Record<string, unknown>) ?? {}
423+
return {
424+
eventType: event.event_type ?? null,
425+
workspaceId: id.workspace_id ?? null,
426+
listId: id.list_id ?? null,
427+
entryId: id.entry_id ?? null,
428+
}
429+
}
430+
431+
/**
432+
* Extracts formatted data from an Attio list-entry.updated event payload.
433+
*/
434+
export function extractAttioListEntryUpdatedData(
435+
body: Record<string, unknown>
436+
): Record<string, unknown> {
437+
const event = getAttioEvent(body) ?? {}
438+
const id = (event.id as Record<string, unknown>) ?? {}
439+
return {
440+
eventType: event.event_type ?? null,
441+
workspaceId: id.workspace_id ?? null,
442+
listId: id.list_id ?? null,
443+
entryId: id.entry_id ?? null,
444+
attributeId: id.attribute_id ?? null,
445+
}
446+
}
447+
448+
/**
449+
* Extracts formatted data from a generic Attio webhook payload.
450+
* Passes through the first event with camelCase field mapping.
451+
*/
452+
export function extractAttioGenericData(body: Record<string, unknown>): Record<string, unknown> {
453+
const event = getAttioEvent(body) ?? {}
454+
const id = (event.id as Record<string, unknown>) ?? {}
455+
return {
456+
eventType: event.event_type ?? null,
457+
id,
458+
parentObjectId: (event.parent_object_id as string) ?? null,
459+
parentRecordId: (event.parent_record_id as string) ?? null,
460+
}
461+
}

0 commit comments

Comments
 (0)