From 79ed7ed04beb3f3d0bf2a770dad059b6e3f4824e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:25:47 -0800 Subject: [PATCH 01/14] fix(attio): use code subblock type for JSON input fields --- apps/sim/blocks/blocks/attio.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 50dae4555d..ca297fec04 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -139,7 +139,7 @@ export const AttioBlock: BlockConfig = { { id: 'values', title: 'Values', - type: 'long-input', + type: 'code', placeholder: '{"name": "Acme Corp", "domains": [{"domain": "acme.com"}]}', condition: { field: 'operation', @@ -184,7 +184,7 @@ Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]} { id: 'filter', title: 'Filter', - type: 'long-input', + type: 'code', placeholder: '{"name": "John Smith"} (optional)', condition: { field: 'operation', value: 'list_records' }, wandConfig: { @@ -215,7 +215,7 @@ Empty (list all): {}`, { id: 'sorts', title: 'Sort', - type: 'long-input', + type: 'code', placeholder: '[{"direction":"asc","attribute":"name"}] (optional)', condition: { field: 'operation', value: 'list_records' }, wandConfig: { @@ -394,7 +394,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ { id: 'taskLinkedRecords', title: 'Linked Records', - type: 'long-input', + type: 'code', placeholder: '[{"target_object":"people","target_record_id":"..."}] (optional)', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { @@ -420,7 +420,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. { id: 'taskAssignees', title: 'Assignees', - type: 'long-input', + type: 'code', placeholder: '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}] (optional)', condition: { field: 'operation', value: ['create_task', 'update_task'] }, @@ -622,7 +622,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. { id: 'entryValues', title: 'Entry Values', - type: 'long-input', + type: 'code', placeholder: '{"attribute_slug": "value"} (optional)', condition: { field: 'operation', @@ -652,7 +652,7 @@ Keys are list attribute slugs. Values follow Attio attribute format. { id: 'entryFilter', title: 'Filter', - type: 'long-input', + type: 'code', placeholder: '{"attribute": {"$operator": "value"}} (optional)', condition: { field: 'operation', value: 'query_list_entries' }, wandConfig: { @@ -679,7 +679,7 @@ Logical: $and, $or, $not { id: 'entrySorts', title: 'Sort', - type: 'long-input', + type: 'code', placeholder: '[{"direction":"asc","attribute":"created_at"}] (optional)', condition: { field: 'operation', value: 'query_list_entries' }, wandConfig: { @@ -870,7 +870,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ { id: 'webhookSubscriptions', title: 'Subscriptions', - type: 'long-input', + type: 'code', placeholder: '[{"event_type":"record.created","filter":{"object_id":"..."}}]', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, required: { field: 'operation', value: 'create_webhook' }, From 341cc8b82fdbbac45443383e6d11f03f2fbc379a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:32:23 -0800 Subject: [PATCH 02/14] fix(attio): correct people name attribute format in wand prompt example --- apps/sim/blocks/blocks/attio.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index ca297fec04..7a7b04786f 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -175,7 +175,7 @@ Keys are attribute slugs, values follow Attio's attribute format. Simple values - description, primary_location, categories ### EXAMPLES -Person: {"name": [{"first_name": "John", "last_name": "Doe"}], "email_addresses": [{"email_address": "john@example.com"}]} +Person: {"name": {"first_name": "John", "last_name": "Doe"}, "email_addresses": [{"email_address": "john@example.com"}]} Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]}`, placeholder: 'Describe the record values you want to set...', generationType: 'json-object', From 1585100224a97d801988b0e23a7accd2e174a951 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:34:04 -0800 Subject: [PATCH 03/14] fix(attio): improve wand prompt with correct attribute formats for all field types --- apps/sim/blocks/blocks/attio.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 7a7b04786f..5c8ec25853 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -161,21 +161,25 @@ export const AttioBlock: BlockConfig = { Return ONLY the JSON object with Attio attribute values. No explanations, no markdown, no extra text. ### ATTIO VALUES STRUCTURE -Keys are attribute slugs, values follow Attio's attribute format. Simple values can be strings; complex values are arrays of objects. +Keys are attribute slugs. Most text attributes use array-of-objects format [{"value": "..."}]. Special attributes have their own format. ### COMMON PEOPLE ATTRIBUTES -- name: [{"first_name": "...", "last_name": "..."}] +- name: {"first_name": "...", "last_name": "..."} (plain object, NOT an array) - email_addresses: [{"email_address": "..."}] - phone_numbers: [{"original_phone_number": "...", "country_code": "US"}] -- job_title, description, linkedin, twitter +- job_title: [{"value": "..."}] +- description: [{"value": "..."}] +- linkedin: [{"value": "https://linkedin.com/in/..."}] +- twitter: [{"value": "@handle"}] ### COMMON COMPANY ATTRIBUTES - name: [{"value": "..."}] - domains: [{"domain": "..."}] -- description, primary_location, categories +- description: [{"value": "..."}] +- primary_location: [{"line_1": "...", "locality": "...", "region": "...", "postcode": "...", "country_code": "US"}] ### EXAMPLES -Person: {"name": {"first_name": "John", "last_name": "Doe"}, "email_addresses": [{"email_address": "john@example.com"}]} +Person: {"name": {"first_name": "John", "last_name": "Doe"}, "email_addresses": [{"email_address": "john@example.com"}], "job_title": [{"value": "Engineer"}]} Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]}`, placeholder: 'Describe the record values you want to set...', generationType: 'json-object', From ca20d6f1fb9444f6ca36b824811e997bc758f20d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:35:59 -0800 Subject: [PATCH 04/14] fix(attio): use array format with full_name for personal-name attribute in wand prompt --- apps/sim/blocks/blocks/attio.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 5c8ec25853..a625e6a110 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -164,7 +164,7 @@ Return ONLY the JSON object with Attio attribute values. No explanations, no mar Keys are attribute slugs. Most text attributes use array-of-objects format [{"value": "..."}]. Special attributes have their own format. ### COMMON PEOPLE ATTRIBUTES -- name: {"first_name": "...", "last_name": "..."} (plain object, NOT an array) +- name: [{"first_name": "...", "last_name": "...", "full_name": "..."}] (personal-name type, full_name is required) - email_addresses: [{"email_address": "..."}] - phone_numbers: [{"original_phone_number": "...", "country_code": "US"}] - job_title: [{"value": "..."}] @@ -179,7 +179,7 @@ Keys are attribute slugs. Most text attributes use array-of-objects format [{"va - primary_location: [{"line_1": "...", "locality": "...", "region": "...", "postcode": "...", "country_code": "US"}] ### EXAMPLES -Person: {"name": {"first_name": "John", "last_name": "Doe"}, "email_addresses": [{"email_address": "john@example.com"}], "job_title": [{"value": "Engineer"}]} +Person: {"name": [{"first_name": "John", "last_name": "Doe", "full_name": "John Doe"}], "email_addresses": [{"email_address": "john@example.com"}], "job_title": [{"value": "Engineer"}]} Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]}`, placeholder: 'Describe the record values you want to set...', generationType: 'json-object', From 9a28f3b9f63c1f1f0e552e37d9b647df4d11709a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:40:37 -0800 Subject: [PATCH 05/14] fix(attio): use loose null checks to prevent sending null params to API --- apps/sim/tools/attio/create_note.ts | 2 +- apps/sim/tools/attio/list_notes.ts | 4 ++-- apps/sim/tools/attio/list_records.ts | 4 ++-- apps/sim/tools/attio/list_tasks.ts | 6 +++--- apps/sim/tools/attio/list_threads.ts | 4 ++-- apps/sim/tools/attio/list_webhooks.ts | 4 ++-- apps/sim/tools/attio/query_list_entries.ts | 4 ++-- apps/sim/tools/attio/update_list.ts | 8 ++++---- apps/sim/tools/attio/update_object.ts | 6 +++--- apps/sim/tools/attio/update_task.ts | 4 ++-- apps/sim/tools/attio/update_webhook.ts | 2 +- 11 files changed, 24 insertions(+), 24 deletions(-) diff --git a/apps/sim/tools/attio/create_note.ts b/apps/sim/tools/attio/create_note.ts index 6483fa51e2..3124959a92 100644 --- a/apps/sim/tools/attio/create_note.ts +++ b/apps/sim/tools/attio/create_note.ts @@ -83,7 +83,7 @@ export const attioCreateNoteTool: ToolConfig { const searchParams = new URLSearchParams() - if (params.limit !== undefined) searchParams.set('limit', String(params.limit)) - if (params.offset !== undefined) searchParams.set('offset', String(params.offset)) + if (params.limit != null) searchParams.set('limit', String(params.limit)) + if (params.offset != null) searchParams.set('offset', String(params.offset)) const qs = searchParams.toString() return `https://api.attio.com/v2/webhooks${qs ? `?${qs}` : ''}` }, diff --git a/apps/sim/tools/attio/query_list_entries.ts b/apps/sim/tools/attio/query_list_entries.ts index a876b6bf18..d234c2aec3 100644 --- a/apps/sim/tools/attio/query_list_entries.ts +++ b/apps/sim/tools/attio/query_list_entries.ts @@ -83,8 +83,8 @@ export const attioQueryListEntriesTool: ToolConfig< body.sorts = [] } } - if (params.limit !== undefined) body.limit = params.limit - if (params.offset !== undefined) body.offset = params.offset + if (params.limit != null) body.limit = params.limit + if (params.offset != null) body.offset = params.offset return body }, }, diff --git a/apps/sim/tools/attio/update_list.ts b/apps/sim/tools/attio/update_list.ts index 798949bae4..e54176a128 100644 --- a/apps/sim/tools/attio/update_list.ts +++ b/apps/sim/tools/attio/update_list.ts @@ -66,10 +66,10 @@ export const attioUpdateListTool: ToolConfig { const data: Record = {} - if (params.name !== undefined) data.name = params.name - if (params.apiSlug !== undefined) data.api_slug = params.apiSlug - if (params.workspaceAccess !== undefined) data.workspace_access = params.workspaceAccess - if (params.workspaceMemberAccess !== undefined) { + if (params.name != null) data.name = params.name + if (params.apiSlug != null) data.api_slug = params.apiSlug + if (params.workspaceAccess != null) data.workspace_access = params.workspaceAccess + if (params.workspaceMemberAccess != null) { try { data.workspace_member_access = typeof params.workspaceMemberAccess === 'string' diff --git a/apps/sim/tools/attio/update_object.ts b/apps/sim/tools/attio/update_object.ts index cd220e508d..1b8c0024a9 100644 --- a/apps/sim/tools/attio/update_object.ts +++ b/apps/sim/tools/attio/update_object.ts @@ -59,9 +59,9 @@ export const attioUpdateObjectTool: ToolConfig { const data: Record = {} - if (params.apiSlug !== undefined) data.api_slug = params.apiSlug - if (params.singularNoun !== undefined) data.singular_noun = params.singularNoun - if (params.pluralNoun !== undefined) data.plural_noun = params.pluralNoun + if (params.apiSlug != null) data.api_slug = params.apiSlug + if (params.singularNoun != null) data.singular_noun = params.singularNoun + if (params.pluralNoun != null) data.plural_noun = params.pluralNoun return { data } }, }, diff --git a/apps/sim/tools/attio/update_task.ts b/apps/sim/tools/attio/update_task.ts index b22e9e014a..9b1e4e21e8 100644 --- a/apps/sim/tools/attio/update_task.ts +++ b/apps/sim/tools/attio/update_task.ts @@ -64,8 +64,8 @@ export const attioUpdateTaskTool: ToolConfig { const data: Record = {} - if (params.deadlineAt !== undefined) data.deadline_at = params.deadlineAt || null - if (params.isCompleted !== undefined) data.is_completed = params.isCompleted + if (params.deadlineAt != null) data.deadline_at = params.deadlineAt || null + if (params.isCompleted != null) data.is_completed = params.isCompleted if (params.linkedRecords) { try { data.linked_records = diff --git a/apps/sim/tools/attio/update_webhook.ts b/apps/sim/tools/attio/update_webhook.ts index d40e478473..1b6ef56fab 100644 --- a/apps/sim/tools/attio/update_webhook.ts +++ b/apps/sim/tools/attio/update_webhook.ts @@ -55,7 +55,7 @@ export const attioUpdateWebhookTool: ToolConfig< }), body: (params) => { const data: Record = {} - if (params.targetUrl !== undefined) data.target_url = params.targetUrl + if (params.targetUrl != null) data.target_url = params.targetUrl if (params.subscriptions) { try { data.subscriptions = From 7a7bcfeca732898e5147f42dde5164b287b6d3c9 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:44:57 -0800 Subject: [PATCH 06/14] fix(attio): add offset param and make pagination fields advanced mode --- apps/sim/blocks/blocks/attio.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index a625e6a110..209acba26b 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -916,6 +916,7 @@ workspace-member.created title: 'Limit', type: 'short-input', placeholder: 'Max results (optional)', + mode: 'advanced', condition: { field: 'operation', value: [ @@ -929,6 +930,24 @@ workspace-member.created ], }, }, + { + id: 'offset', + title: 'Offset', + type: 'short-input', + placeholder: 'Number of results to skip (optional)', + mode: 'advanced', + condition: { + field: 'operation', + value: [ + 'list_records', + 'list_notes', + 'list_tasks', + 'query_list_entries', + 'list_threads', + 'list_webhooks', + ], + }, + }, ...getTrigger('attio_record_created').subBlocks, ...getTrigger('attio_record_updated').subBlocks, ...getTrigger('attio_record_deleted').subBlocks, @@ -1107,6 +1126,7 @@ workspace-member.created // Shared params if (params.limit) cleanParams.limit = Number(params.limit) + if (params.offset) cleanParams.offset = Number(params.offset) return cleanParams }, @@ -1168,6 +1188,7 @@ workspace-member.created webhookTargetUrl: { type: 'string', description: 'Webhook target URL' }, webhookSubscriptions: { type: 'json', description: 'Webhook event subscriptions' }, limit: { type: 'string', description: 'Maximum number of results' }, + offset: { type: 'string', description: 'Number of results to skip for pagination' }, }, outputs: { From 721c77f381bd6e02fb72ff1978ddf3aa3d3db9d0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 15:50:35 -0800 Subject: [PATCH 07/14] fix(attio): remove redundant (optional) from placeholders --- apps/sim/blocks/blocks/attio.ts | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 209acba26b..3a85f81be8 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -189,7 +189,7 @@ Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]} id: 'filter', title: 'Filter', type: 'code', - placeholder: '{"name": "John Smith"} (optional)', + placeholder: '{"name": "John Smith"}', condition: { field: 'operation', value: 'list_records' }, wandConfig: { enabled: true, @@ -220,7 +220,7 @@ Empty (list all): {}`, id: 'sorts', title: 'Sort', type: 'code', - placeholder: '[{"direction":"asc","attribute":"name"}] (optional)', + placeholder: '[{"direction":"asc","attribute":"name"}]', condition: { field: 'operation', value: 'list_records' }, wandConfig: { enabled: true, @@ -336,7 +336,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'noteMeetingId', title: 'Meeting ID', type: 'short-input', - placeholder: 'Link to a meeting (optional)', + placeholder: 'Link to a meeting', condition: { field: 'operation', value: 'create_note' }, mode: 'advanced', }, @@ -362,7 +362,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'taskDeadline', title: 'Deadline', type: 'short-input', - placeholder: '2024-12-01T15:00:00.000Z (optional)', + placeholder: '2024-12-01T15:00:00.000Z', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -399,7 +399,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'taskLinkedRecords', title: 'Linked Records', type: 'code', - placeholder: '[{"target_object":"people","target_record_id":"..."}] (optional)', + placeholder: '[{"target_object":"people","target_record_id":"..."}]', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -426,7 +426,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. title: 'Assignees', type: 'code', placeholder: - '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}] (optional)', + '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}]', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -460,21 +460,21 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. id: 'taskFilterObject', title: 'Linked Object Type', type: 'short-input', - placeholder: 'e.g. people, companies (optional)', + placeholder: 'e.g. people, companies', condition: { field: 'operation', value: 'list_tasks' }, }, { id: 'taskFilterRecordId', title: 'Linked Record ID', type: 'short-input', - placeholder: 'Filter by linked record ID (optional)', + placeholder: 'Filter by linked record ID', condition: { field: 'operation', value: 'list_tasks' }, }, { id: 'taskFilterAssignee', title: 'Assignee', type: 'short-input', - placeholder: 'Filter by assignee email or ID (optional)', + placeholder: 'Filter by assignee email or ID', condition: { field: 'operation', value: 'list_tasks' }, }, { @@ -627,7 +627,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. id: 'entryValues', title: 'Entry Values', type: 'code', - placeholder: '{"attribute_slug": "value"} (optional)', + placeholder: '{"attribute_slug": "value"}', condition: { field: 'operation', value: ['create_list_entry', 'update_list_entry'], @@ -657,7 +657,7 @@ Keys are list attribute slugs. Values follow Attio attribute format. id: 'entryFilter', title: 'Filter', type: 'code', - placeholder: '{"attribute": {"$operator": "value"}} (optional)', + placeholder: '{"attribute": {"$operator": "value"}}', condition: { field: 'operation', value: 'query_list_entries' }, wandConfig: { enabled: true, @@ -684,7 +684,7 @@ Logical: $and, $or, $not id: 'entrySorts', title: 'Sort', type: 'code', - placeholder: '[{"direction":"asc","attribute":"created_at"}] (optional)', + placeholder: '[{"direction":"asc","attribute":"created_at"}]', condition: { field: 'operation', value: 'query_list_entries' }, wandConfig: { enabled: true, @@ -823,7 +823,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'threadFilterRecordId', title: 'Record ID', type: 'short-input', - placeholder: 'Filter by record ID (optional)', + placeholder: 'Filter by record ID', condition: { field: 'operation', value: 'list_threads' }, }, { @@ -837,7 +837,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ id: 'threadFilterEntryId', title: 'Entry ID', type: 'short-input', - placeholder: 'Filter by entry ID (optional)', + placeholder: 'Filter by entry ID', condition: { field: 'operation', value: 'list_threads' }, }, { @@ -915,7 +915,7 @@ workspace-member.created id: 'limit', title: 'Limit', type: 'short-input', - placeholder: 'Max results (optional)', + placeholder: 'Max results', mode: 'advanced', condition: { field: 'operation', @@ -934,7 +934,7 @@ workspace-member.created id: 'offset', title: 'Offset', type: 'short-input', - placeholder: 'Number of results to skip (optional)', + placeholder: 'Number of results to skip', mode: 'advanced', condition: { field: 'operation', From 05419cc64df7eaa4b642bfbc116a1252c92f071d Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 16:04:52 -0800 Subject: [PATCH 08/14] fix(attio): always send required workspace_access and workspace_member_access in create list --- apps/sim/tools/attio/create_list.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/sim/tools/attio/create_list.ts b/apps/sim/tools/attio/create_list.ts index a41d7c5396..799d3da31c 100644 --- a/apps/sim/tools/attio/create_list.ts +++ b/apps/sim/tools/attio/create_list.ts @@ -68,9 +68,10 @@ export const attioCreateListTool: ToolConfig = { name: params.name, parent_object: params.parentObject, + workspace_access: params.workspaceAccess ?? null, + workspace_member_access: [] as unknown[], } if (params.apiSlug) data.api_slug = params.apiSlug - if (params.workspaceAccess) data.workspace_access = params.workspaceAccess if (params.workspaceMemberAccess) { try { data.workspace_member_access = From 9fd2ff090416cb4e6a2f35e8f855f0ec9312e52e Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 16:06:28 -0800 Subject: [PATCH 09/14] fix(attio): always send api_slug in create list, auto-generate from name if not provided --- apps/sim/tools/attio/create_list.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/sim/tools/attio/create_list.ts b/apps/sim/tools/attio/create_list.ts index 799d3da31c..471e30976c 100644 --- a/apps/sim/tools/attio/create_list.ts +++ b/apps/sim/tools/attio/create_list.ts @@ -65,13 +65,15 @@ export const attioCreateListTool: ToolConfig { + const apiSlug = + params.apiSlug || params.name?.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '') const data: Record = { name: params.name, + api_slug: apiSlug, parent_object: params.parentObject, workspace_access: params.workspaceAccess ?? null, workspace_member_access: [] as unknown[], } - if (params.apiSlug) data.api_slug = params.apiSlug if (params.workspaceMemberAccess) { try { data.workspace_member_access = From 5cbb71244eba8cf89b500ec802e9d78ae7525703 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 16:07:08 -0800 Subject: [PATCH 10/14] fix(attio): update api slug placeholder text --- apps/sim/blocks/blocks/attio.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index 3a85f81be8..da7ea27bce 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -575,7 +575,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. id: 'listApiSlug', title: 'API Slug', type: 'short-input', - placeholder: 'e.g. my_list (optional, auto-generated)', + placeholder: 'e.g. my_list (auto-generated from name)', condition: { field: 'operation', value: ['create_list', 'update_list'] }, mode: 'advanced', }, From d9dec98cc1b9f701dad52fd6b13f47353219d569 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 16:45:34 -0800 Subject: [PATCH 11/14] fix(tools): manage lifecycle for attio tools --- apps/sim/blocks/blocks/attio.ts | 7 +- apps/sim/lib/webhooks/processor.ts | 24 ++ .../lib/webhooks/provider-subscriptions.ts | 207 +++++++++++++++++- apps/sim/lib/webhooks/utils.server.ts | 35 +++ apps/sim/tools/attio/create_list.ts | 6 +- apps/sim/tools/attio/create_list_entry.ts | 14 +- apps/sim/tools/attio/update_webhook.ts | 19 +- apps/sim/triggers/attio/comment_created.ts | 15 +- apps/sim/triggers/attio/comment_deleted.ts | 15 +- apps/sim/triggers/attio/comment_resolved.ts | 15 +- apps/sim/triggers/attio/comment_unresolved.ts | 15 +- apps/sim/triggers/attio/list_entry_created.ts | 15 +- apps/sim/triggers/attio/list_entry_deleted.ts | 15 +- apps/sim/triggers/attio/list_entry_updated.ts | 15 +- apps/sim/triggers/attio/note_created.ts | 15 +- apps/sim/triggers/attio/note_deleted.ts | 15 +- apps/sim/triggers/attio/note_updated.ts | 15 +- apps/sim/triggers/attio/record_created.ts | 23 +- apps/sim/triggers/attio/record_deleted.ts | 15 +- apps/sim/triggers/attio/record_merged.ts | 15 +- apps/sim/triggers/attio/record_updated.ts | 15 +- apps/sim/triggers/attio/task_created.ts | 15 +- apps/sim/triggers/attio/task_deleted.ts | 15 +- apps/sim/triggers/attio/task_updated.ts | 15 +- apps/sim/triggers/attio/utils.ts | 50 +++-- apps/sim/triggers/attio/webhook.ts | 15 +- 26 files changed, 371 insertions(+), 269 deletions(-) diff --git a/apps/sim/blocks/blocks/attio.ts b/apps/sim/blocks/blocks/attio.ts index da7ea27bce..8e51dbbe71 100644 --- a/apps/sim/blocks/blocks/attio.ts +++ b/apps/sim/blocks/blocks/attio.ts @@ -425,8 +425,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text. id: 'taskAssignees', title: 'Assignees', type: 'code', - placeholder: - '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}]', + placeholder: '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}]', condition: { field: 'operation', value: ['create_task', 'update_task'] }, wandConfig: { enabled: true, @@ -869,7 +868,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ type: 'short-input', placeholder: 'https://example.com/webhook', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, - required: { field: 'operation', value: 'create_webhook' }, + required: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, }, { id: 'webhookSubscriptions', @@ -877,7 +876,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ type: 'code', placeholder: '[{"event_type":"record.created","filter":{"object_id":"..."}}]', condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, - required: { field: 'operation', value: 'create_webhook' }, + required: { field: 'operation', value: ['create_webhook', 'update_webhook'] }, wandConfig: { enabled: true, maintainHistory: true, diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 0602f0b85c..80cc0147b4 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -13,6 +13,7 @@ import { convertSquareBracketsToTwiML } from '@/lib/webhooks/utils' import { handleSlackChallenge, handleWhatsAppVerification, + validateAttioSignature, validateCalcomSignature, validateCirclebackSignature, validateFirefliesSignature, @@ -597,6 +598,29 @@ export async function verifyProviderAuth( } } + if (foundWebhook.provider === 'attio') { + const secret = providerConfig.webhookSecret as string | undefined + + if (secret) { + const signature = request.headers.get('Attio-Signature') + + if (!signature) { + logger.warn(`[${requestId}] Attio webhook missing signature header`) + return new NextResponse('Unauthorized - Missing Attio signature', { status: 401 }) + } + + const isValidSignature = validateAttioSignature(secret, signature, rawBody) + + if (!isValidSignature) { + logger.warn(`[${requestId}] Attio signature verification failed`, { + signatureLength: signature.length, + secretLength: secret.length, + }) + return new NextResponse('Unauthorized - Invalid Attio signature', { status: 401 }) + } + } + } + if (foundWebhook.provider === 'linear') { const secret = providerConfig.webhookSecret as string | undefined diff --git a/apps/sim/lib/webhooks/provider-subscriptions.ts b/apps/sim/lib/webhooks/provider-subscriptions.ts index 10c1205711..7bb100a09e 100644 --- a/apps/sim/lib/webhooks/provider-subscriptions.ts +++ b/apps/sim/lib/webhooks/provider-subscriptions.ts @@ -19,6 +19,7 @@ const calendlyLogger = createLogger('CalendlyWebhook') const grainLogger = createLogger('GrainWebhook') const lemlistLogger = createLogger('LemlistWebhook') const webflowLogger = createLogger('WebflowWebhook') +const attioLogger = createLogger('AttioWebhook') const providerSubscriptionsLogger = createLogger('WebhookProviderSubscriptions') function getProviderConfig(webhook: any): Record { @@ -976,6 +977,196 @@ export async function deleteWebflowWebhook( } } +export async function createAttioWebhookSubscription( + userId: string, + webhookData: any, + requestId: string +): Promise<{ externalId: string; webhookSecret: string } | undefined> { + try { + const { path, providerConfig } = webhookData + const { triggerId, credentialId } = providerConfig || {} + + if (!credentialId) { + attioLogger.warn(`[${requestId}] Missing credentialId for Attio webhook creation.`, { + webhookId: webhookData.id, + }) + throw new Error( + 'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.' + ) + } + + const credentialOwner = await getCredentialOwner(credentialId, requestId) + const accessToken = credentialOwner + ? await refreshAccessTokenIfNeeded( + credentialOwner.accountId, + credentialOwner.userId, + requestId + ) + : null + + if (!accessToken) { + attioLogger.warn( + `[${requestId}] Could not retrieve Attio access token for user ${userId}. Cannot create webhook.` + ) + throw new Error( + 'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.' + ) + } + + const notificationUrl = `${getBaseUrl()}/api/webhooks/trigger/${path}` + + const { TRIGGER_EVENT_MAP } = await import('@/triggers/attio/utils') + + let subscriptions: Array<{ event_type: string }> = [] + if (triggerId === 'attio_webhook') { + const allEvents = new Set() + for (const events of Object.values(TRIGGER_EVENT_MAP)) { + for (const event of events) { + allEvents.add(event) + } + } + subscriptions = Array.from(allEvents).map((event_type) => ({ event_type })) + } else { + const events = TRIGGER_EVENT_MAP[triggerId] + if (!events || events.length === 0) { + attioLogger.warn(`[${requestId}] No event types mapped for trigger ${triggerId}`, { + webhookId: webhookData.id, + }) + throw new Error(`Unknown Attio trigger type: ${triggerId}`) + } + subscriptions = events.map((event_type) => ({ event_type })) + } + + const requestBody = { + target_url: notificationUrl, + subscriptions, + } + + const attioResponse = await fetch('https://api.attio.com/v2/webhooks', { + method: 'POST', + headers: { + Authorization: `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify(requestBody), + }) + + if (!attioResponse.ok) { + const errorBody = await attioResponse.json().catch(() => ({})) + attioLogger.error( + `[${requestId}] Failed to create webhook in Attio for webhook ${webhookData.id}. Status: ${attioResponse.status}`, + { response: errorBody } + ) + + let userFriendlyMessage = 'Failed to create webhook subscription in Attio' + if (attioResponse.status === 401) { + userFriendlyMessage = 'Attio authentication failed. Please reconnect your Attio account.' + } else if (attioResponse.status === 403) { + userFriendlyMessage = + 'Attio access denied. Please ensure your integration has webhook permissions.' + } + + throw new Error(userFriendlyMessage) + } + + const responseBody = await attioResponse.json() + const data = responseBody.data || responseBody + const webhookId = data.id?.webhook_id || data.webhook_id || data.id + const secret = data.secret + + if (!webhookId) { + attioLogger.error( + `[${requestId}] Attio webhook created but no webhook_id returned for webhook ${webhookData.id}`, + { response: responseBody } + ) + throw new Error('Attio webhook creation succeeded but no webhook ID was returned') + } + + if (!secret) { + attioLogger.warn( + `[${requestId}] Attio webhook created but no secret returned for webhook ${webhookData.id}. Signature verification will be skipped.`, + { response: responseBody } + ) + } + + attioLogger.info( + `[${requestId}] Successfully created webhook in Attio for webhook ${webhookData.id}.`, + { attioWebhookId: webhookId } + ) + + return { externalId: webhookId, webhookSecret: secret || '' } + } catch (error: unknown) { + const message = error instanceof Error ? error.message : String(error) + attioLogger.error( + `[${requestId}] Exception during Attio webhook creation for webhook ${webhookData.id}.`, + { message } + ) + throw error + } +} + +export async function deleteAttioWebhook( + webhook: any, + _workflow: any, + requestId: string +): Promise { + try { + const config = getProviderConfig(webhook) + const externalId = config.externalId as string | undefined + const credentialId = config.credentialId as string | undefined + + if (!externalId) { + attioLogger.warn( + `[${requestId}] Missing externalId for Attio webhook deletion ${webhook.id}, skipping cleanup` + ) + return + } + + if (!credentialId) { + attioLogger.warn( + `[${requestId}] Missing credentialId for Attio webhook deletion ${webhook.id}, skipping cleanup` + ) + return + } + + const credentialOwner = await getCredentialOwner(credentialId, requestId) + const accessToken = credentialOwner + ? await refreshAccessTokenIfNeeded( + credentialOwner.accountId, + credentialOwner.userId, + requestId + ) + : null + + if (!accessToken) { + attioLogger.warn( + `[${requestId}] Could not retrieve Attio access token. Cannot delete webhook.`, + { webhookId: webhook.id } + ) + return + } + + const attioResponse = await fetch(`https://api.attio.com/v2/webhooks/${externalId}`, { + method: 'DELETE', + headers: { + Authorization: `Bearer ${accessToken}`, + }, + }) + + if (!attioResponse.ok && attioResponse.status !== 404) { + const responseBody = await attioResponse.json().catch(() => ({})) + attioLogger.warn( + `[${requestId}] Failed to delete Attio webhook (non-fatal): ${attioResponse.status}`, + { response: responseBody } + ) + } else { + attioLogger.info(`[${requestId}] Successfully deleted Attio webhook ${externalId}`) + } + } catch (error) { + attioLogger.warn(`[${requestId}] Error deleting Attio webhook (non-fatal)`, error) + } +} + export async function createGrainWebhookSubscription( _request: NextRequest, webhookData: any, @@ -1611,6 +1802,7 @@ type RecreateCheckInput = { /** Providers that create external webhook subscriptions */ const PROVIDERS_WITH_EXTERNAL_SUBSCRIPTIONS = new Set([ 'airtable', + 'attio', 'calendly', 'webflow', 'typeform', @@ -1626,6 +1818,7 @@ const SYSTEM_MANAGED_FIELDS = new Set([ 'externalSubscriptionId', 'eventTypes', 'webhookTag', + 'webhookSecret', 'historyId', 'lastCheckedTimestamp', 'setupCompleted', @@ -1686,6 +1879,16 @@ export async function createExternalWebhookSubscription( updatedProviderConfig = { ...updatedProviderConfig, externalId } externalSubscriptionCreated = true } + } else if (provider === 'attio') { + const result = await createAttioWebhookSubscription(userId, webhookData, requestId) + if (result) { + updatedProviderConfig = { + ...updatedProviderConfig, + externalId: result.externalId, + webhookSecret: result.webhookSecret, + } + externalSubscriptionCreated = true + } } else if (provider === 'calendly') { const externalId = await createCalendlyWebhookSubscription(webhookData, requestId) if (externalId) { @@ -1736,7 +1939,7 @@ export async function createExternalWebhookSubscription( /** * Clean up external webhook subscriptions for a webhook - * Handles Airtable, Teams, Telegram, Typeform, Calendly, Grain, and Lemlist cleanup + * Handles Airtable, Attio, Teams, Telegram, Typeform, Calendly, Grain, and Lemlist cleanup * Don't fail deletion if cleanup fails */ export async function cleanupExternalWebhook( @@ -1746,6 +1949,8 @@ export async function cleanupExternalWebhook( ): Promise { if (webhook.provider === 'airtable') { await deleteAirtableWebhook(webhook, workflow, requestId) + } else if (webhook.provider === 'attio') { + await deleteAttioWebhook(webhook, workflow, requestId) } else if (webhook.provider === 'microsoft-teams') { await deleteTeamsSubscription(webhook, workflow, requestId) } else if (webhook.provider === 'telegram') { diff --git a/apps/sim/lib/webhooks/utils.server.ts b/apps/sim/lib/webhooks/utils.server.ts index f63983577b..7d9abfcef7 100644 --- a/apps/sim/lib/webhooks/utils.server.ts +++ b/apps/sim/lib/webhooks/utils.server.ts @@ -1395,6 +1395,41 @@ export function validateLinearSignature(secret: string, signature: string, body: } } +/** + * Validates an Attio webhook request signature using HMAC SHA-256 + * @param secret - Attio webhook signing secret (plain text) + * @param signature - Attio-Signature header value (hex-encoded HMAC SHA-256 signature) + * @param body - Raw request body string + * @returns Whether the signature is valid + */ +export function validateAttioSignature(secret: string, signature: string, body: string): boolean { + try { + if (!secret || !signature || !body) { + logger.warn('Attio signature validation missing required fields', { + hasSecret: !!secret, + hasSignature: !!signature, + hasBody: !!body, + }) + return false + } + + const computedHash = crypto.createHmac('sha256', secret).update(body, 'utf8').digest('hex') + + logger.debug('Attio signature comparison', { + computedSignature: `${computedHash.substring(0, 10)}...`, + providedSignature: `${signature.substring(0, 10)}...`, + computedLength: computedHash.length, + providedLength: signature.length, + match: computedHash === signature, + }) + + return safeCompare(computedHash, signature) + } catch (error) { + logger.error('Error validating Attio signature:', error) + return false + } +} + /** * Validates a Circleback webhook request signature using HMAC SHA-256 * @param secret - Circleback signing secret (plain text) diff --git a/apps/sim/tools/attio/create_list.ts b/apps/sim/tools/attio/create_list.ts index 471e30976c..eb2d344722 100644 --- a/apps/sim/tools/attio/create_list.ts +++ b/apps/sim/tools/attio/create_list.ts @@ -66,7 +66,11 @@ export const attioCreateListTool: ToolConfig { const apiSlug = - params.apiSlug || params.name?.toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_|_$/g, '') + params.apiSlug || + params.name + ?.toLowerCase() + .replace(/[^a-z0-9]+/g, '_') + .replace(/^_|_$/g, '') const data: Record = { name: params.name, api_slug: apiSlug, diff --git a/apps/sim/tools/attio/create_list_entry.ts b/apps/sim/tools/attio/create_list_entry.ts index 7c12a38864..381e61e1d7 100644 --- a/apps/sim/tools/attio/create_list_entry.ts +++ b/apps/sim/tools/attio/create_list_entry.ts @@ -60,20 +60,22 @@ export const attioCreateListEntryTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const data: Record = { - parent_record_id: params.parentRecordId, - parent_object: params.parentObject, - } + let entryValues: unknown = {} if (params.entryValues) { try { - data.entry_values = + entryValues = typeof params.entryValues === 'string' ? JSON.parse(params.entryValues) : params.entryValues } catch { - data.entry_values = {} + entryValues = {} } } + const data: Record = { + parent_record_id: params.parentRecordId, + parent_object: params.parentObject, + entry_values: entryValues, + } return { data } }, }, diff --git a/apps/sim/tools/attio/update_webhook.ts b/apps/sim/tools/attio/update_webhook.ts index 1b6ef56fab..662691c4fd 100644 --- a/apps/sim/tools/attio/update_webhook.ts +++ b/apps/sim/tools/attio/update_webhook.ts @@ -34,15 +34,15 @@ export const attioUpdateWebhookTool: ToolConfig< }, targetUrl: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', - description: 'New HTTPS target URL', + description: 'HTTPS target URL for webhook delivery', }, subscriptions: { type: 'string', - required: false, + required: true, visibility: 'user-or-llm', - description: 'New JSON array of subscriptions', + description: 'JSON array of subscriptions, e.g. [{"event_type":"note.created"}]', }, }, @@ -54,18 +54,21 @@ export const attioUpdateWebhookTool: ToolConfig< 'Content-Type': 'application/json', }), body: (params) => { - const data: Record = {} - if (params.targetUrl != null) data.target_url = params.targetUrl + let subscriptions: unknown = [] if (params.subscriptions) { try { - data.subscriptions = + subscriptions = typeof params.subscriptions === 'string' ? JSON.parse(params.subscriptions) : params.subscriptions } catch { - data.subscriptions = [] + subscriptions = [] } } + const data: Record = { + target_url: params.targetUrl, + subscriptions, + } return { data } }, }, diff --git a/apps/sim/triggers/attio/comment_created.ts b/apps/sim/triggers/attio/comment_created.ts index 721d1b5d51..7fdb6c0811 100644 --- a/apps/sim/triggers/attio/comment_created.ts +++ b/apps/sim/triggers/attio/comment_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.created'), - extraFields: buildAttioExtraFields('attio_comment_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_created'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/comment_deleted.ts b/apps/sim/triggers/attio/comment_deleted.ts index 8f3830de28..b784486fe8 100644 --- a/apps/sim/triggers/attio/comment_deleted.ts +++ b/apps/sim/triggers/attio/comment_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.deleted'), - extraFields: buildAttioExtraFields('attio_comment_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_deleted'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/comment_resolved.ts b/apps/sim/triggers/attio/comment_resolved.ts index e0b1cb7d96..5bda970782 100644 --- a/apps/sim/triggers/attio/comment_resolved.ts +++ b/apps/sim/triggers/attio/comment_resolved.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentResolvedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_resolved', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.resolved'), - extraFields: buildAttioExtraFields('attio_comment_resolved'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_resolved'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/comment_unresolved.ts b/apps/sim/triggers/attio/comment_unresolved.ts index 434cad8814..af0e389969 100644 --- a/apps/sim/triggers/attio/comment_unresolved.ts +++ b/apps/sim/triggers/attio/comment_unresolved.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildCommentOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildCommentOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioCommentUnresolvedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_comment_unresolved', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('comment.unresolved'), - extraFields: buildAttioExtraFields('attio_comment_unresolved'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_comment_unresolved'), outputs: buildCommentOutputs(), diff --git a/apps/sim/triggers/attio/list_entry_created.ts b/apps/sim/triggers/attio/list_entry_created.ts index 8ef9076662..36acba6b03 100644 --- a/apps/sim/triggers/attio/list_entry_created.ts +++ b/apps/sim/triggers/attio/list_entry_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildListEntryOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildListEntryOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioListEntryCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_list_entry_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('list-entry.created'), - extraFields: buildAttioExtraFields('attio_list_entry_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_list_entry_created'), outputs: buildListEntryOutputs(), diff --git a/apps/sim/triggers/attio/list_entry_deleted.ts b/apps/sim/triggers/attio/list_entry_deleted.ts index 72f5911987..be752ce6e4 100644 --- a/apps/sim/triggers/attio/list_entry_deleted.ts +++ b/apps/sim/triggers/attio/list_entry_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildListEntryOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildListEntryOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioListEntryDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_list_entry_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('list-entry.deleted'), - extraFields: buildAttioExtraFields('attio_list_entry_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_list_entry_deleted'), outputs: buildListEntryOutputs(), diff --git a/apps/sim/triggers/attio/list_entry_updated.ts b/apps/sim/triggers/attio/list_entry_updated.ts index 209d553e78..d03bc1142b 100644 --- a/apps/sim/triggers/attio/list_entry_updated.ts +++ b/apps/sim/triggers/attio/list_entry_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildListEntryUpdatedOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildListEntryUpdatedOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioListEntryUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_list_entry_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('list-entry.updated'), - extraFields: buildAttioExtraFields('attio_list_entry_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_list_entry_updated'), outputs: buildListEntryUpdatedOutputs(), diff --git a/apps/sim/triggers/attio/note_created.ts b/apps/sim/triggers/attio/note_created.ts index bb7cfb3b90..d4a5845d9c 100644 --- a/apps/sim/triggers/attio/note_created.ts +++ b/apps/sim/triggers/attio/note_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildNoteOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildNoteOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioNoteCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_note_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('note.created'), - extraFields: buildAttioExtraFields('attio_note_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_note_created'), outputs: buildNoteOutputs(), diff --git a/apps/sim/triggers/attio/note_deleted.ts b/apps/sim/triggers/attio/note_deleted.ts index df453adcb8..76801ec1ac 100644 --- a/apps/sim/triggers/attio/note_deleted.ts +++ b/apps/sim/triggers/attio/note_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildNoteOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildNoteOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioNoteDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_note_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('note.deleted'), - extraFields: buildAttioExtraFields('attio_note_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_note_deleted'), outputs: buildNoteOutputs(), diff --git a/apps/sim/triggers/attio/note_updated.ts b/apps/sim/triggers/attio/note_updated.ts index 886cbd59e8..cf4ee6b9a9 100644 --- a/apps/sim/triggers/attio/note_updated.ts +++ b/apps/sim/triggers/attio/note_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildNoteOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildNoteOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioNoteUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_note_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('note.updated'), - extraFields: buildAttioExtraFields('attio_note_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_note_updated'), outputs: buildNoteOutputs(), diff --git a/apps/sim/triggers/attio/record_created.ts b/apps/sim/triggers/attio/record_created.ts index c1b84c5e8f..c27553d26a 100644 --- a/apps/sim/triggers/attio/record_created.ts +++ b/apps/sim/triggers/attio/record_created.ts @@ -1,9 +1,7 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' import { - attioSetupInstructions, attioTriggerOptions, - buildAttioExtraFields, + buildAttioTriggerSubBlocks, buildRecordOutputs, } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' @@ -22,13 +20,18 @@ export const attioRecordCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_created', - triggerOptions: attioTriggerOptions, - includeDropdown: true, - setupInstructions: attioSetupInstructions('record.created'), - extraFields: buildAttioExtraFields('attio_record_created'), - }), + subBlocks: [ + { + id: 'selectedTriggerId', + title: 'Trigger Type', + type: 'dropdown', + mode: 'trigger', + options: attioTriggerOptions, + value: () => 'attio_record_created', + required: true, + }, + ...buildAttioTriggerSubBlocks('attio_record_created'), + ], outputs: buildRecordOutputs(), diff --git a/apps/sim/triggers/attio/record_deleted.ts b/apps/sim/triggers/attio/record_deleted.ts index f4de4ea537..f71256fd97 100644 --- a/apps/sim/triggers/attio/record_deleted.ts +++ b/apps/sim/triggers/attio/record_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildRecordOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildRecordOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioRecordDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('record.deleted'), - extraFields: buildAttioExtraFields('attio_record_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_record_deleted'), outputs: buildRecordOutputs(), diff --git a/apps/sim/triggers/attio/record_merged.ts b/apps/sim/triggers/attio/record_merged.ts index d611993f41..ca8217d9ce 100644 --- a/apps/sim/triggers/attio/record_merged.ts +++ b/apps/sim/triggers/attio/record_merged.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildRecordMergedEventOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildRecordMergedEventOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioRecordMergedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_merged', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('record.merged'), - extraFields: buildAttioExtraFields('attio_record_merged'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_record_merged'), outputs: buildRecordMergedEventOutputs(), diff --git a/apps/sim/triggers/attio/record_updated.ts b/apps/sim/triggers/attio/record_updated.ts index 12e693d5ba..c64970f86a 100644 --- a/apps/sim/triggers/attio/record_updated.ts +++ b/apps/sim/triggers/attio/record_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildRecordUpdatedOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildRecordUpdatedOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioRecordUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_record_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('record.updated'), - extraFields: buildAttioExtraFields('attio_record_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_record_updated'), outputs: buildRecordUpdatedOutputs(), diff --git a/apps/sim/triggers/attio/task_created.ts b/apps/sim/triggers/attio/task_created.ts index a143802886..e88b7f1680 100644 --- a/apps/sim/triggers/attio/task_created.ts +++ b/apps/sim/triggers/attio/task_created.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildTaskOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildTaskOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioTaskCreatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_task_created', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('task.created'), - extraFields: buildAttioExtraFields('attio_task_created'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_task_created'), outputs: buildTaskOutputs(), diff --git a/apps/sim/triggers/attio/task_deleted.ts b/apps/sim/triggers/attio/task_deleted.ts index 4b19ef594a..a58cac1953 100644 --- a/apps/sim/triggers/attio/task_deleted.ts +++ b/apps/sim/triggers/attio/task_deleted.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildTaskOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildTaskOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioTaskDeletedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_task_deleted', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('task.deleted'), - extraFields: buildAttioExtraFields('attio_task_deleted'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_task_deleted'), outputs: buildTaskOutputs(), diff --git a/apps/sim/triggers/attio/task_updated.ts b/apps/sim/triggers/attio/task_updated.ts index d056dd4e4c..ddcd0c1012 100644 --- a/apps/sim/triggers/attio/task_updated.ts +++ b/apps/sim/triggers/attio/task_updated.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildTaskOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildTaskOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioTaskUpdatedTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_task_updated', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('task.updated'), - extraFields: buildAttioExtraFields('attio_task_updated'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_task_updated'), outputs: buildTaskOutputs(), diff --git a/apps/sim/triggers/attio/utils.ts b/apps/sim/triggers/attio/utils.ts index 3cfa8fc126..535515bc24 100644 --- a/apps/sim/triggers/attio/utils.ts +++ b/apps/sim/triggers/attio/utils.ts @@ -22,15 +22,12 @@ export const attioTriggerOptions = [ { label: 'Generic Webhook (All Events)', id: 'attio_webhook' }, ] -export function attioSetupInstructions(eventType: string): string { +export function attioSetupInstructions(): string { const instructions = [ - 'Note: You need access to the Attio developer settings to create webhooks. See the Attio webhook documentation for details.', - 'In Attio, navigate to Settings > Developers and select your integration.', - 'Go to the Webhooks tab and click "Create Webhook".', - 'Paste the Webhook URL from above into the target URL field.', - `Add a subscription with the event type ${eventType}. You can optionally add filters to scope the events.`, - 'Save the webhook. Copy the signing secret shown and paste it in the field above for signature verification.', - 'The webhook is now active. Attio will send events to the URL you configured.', + 'Note: Webhooks are automatically created in Attio when you deploy this workflow, and deleted when you undeploy. See the Attio webhook documentation for details.', + 'Connect your Attio account using the credential selector above.', + 'Click Save to store your trigger configuration.', + 'Deploy the workflow — a webhook will be created automatically in your Attio workspace.', ] return instructions @@ -41,17 +38,34 @@ export function attioSetupInstructions(eventType: string): string { .join('') } -export function buildAttioExtraFields(triggerId: string): SubBlockConfig[] { +/** + * Builds subBlocks for an Attio trigger with OAuth credentials and automatic webhook lifecycle. + * Used by both the primary trigger (with dropdown) and secondary triggers. + */ +export function buildAttioTriggerSubBlocks(triggerId: string): SubBlockConfig[] { return [ { - id: 'webhookSecret', - title: 'Webhook Secret', - type: 'short-input', - placeholder: 'Enter the webhook signing secret from Attio', - description: - 'The signing secret from Attio used to verify webhook deliveries via HMAC-SHA256 signature', - password: true, - required: false, + id: 'triggerCredentials', + title: 'Attio Account', + type: 'oauth-input', + serviceId: 'attio', + mode: 'trigger', + required: true, + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + { + id: 'triggerSave', + title: 'Save', + type: 'trigger-save', + mode: 'trigger', + condition: { field: 'selectedTriggerId', value: triggerId }, + }, + { + id: 'triggerInstructions', + title: 'Setup Instructions', + hideFromPreview: true, + type: 'text', + defaultValue: attioSetupInstructions(), mode: 'trigger', condition: { field: 'selectedTriggerId', value: triggerId }, }, @@ -257,7 +271,7 @@ export function buildGenericWebhookOutputs(): Record { /** * Maps trigger IDs to the exact Attio event type strings. */ -const TRIGGER_EVENT_MAP: Record = { +export const TRIGGER_EVENT_MAP: Record = { attio_record_created: ['record.created'], attio_record_updated: ['record.updated'], attio_record_deleted: ['record.deleted'], diff --git a/apps/sim/triggers/attio/webhook.ts b/apps/sim/triggers/attio/webhook.ts index 87f615a18e..9bbcd2f119 100644 --- a/apps/sim/triggers/attio/webhook.ts +++ b/apps/sim/triggers/attio/webhook.ts @@ -1,11 +1,5 @@ import { AttioIcon } from '@/components/icons' -import { buildTriggerSubBlocks } from '@/triggers' -import { - attioSetupInstructions, - attioTriggerOptions, - buildAttioExtraFields, - buildGenericWebhookOutputs, -} from '@/triggers/attio/utils' +import { buildAttioTriggerSubBlocks, buildGenericWebhookOutputs } from '@/triggers/attio/utils' import type { TriggerConfig } from '@/triggers/types' /** @@ -21,12 +15,7 @@ export const attioWebhookTrigger: TriggerConfig = { version: '1.0.0', icon: AttioIcon, - subBlocks: buildTriggerSubBlocks({ - triggerId: 'attio_webhook', - triggerOptions: attioTriggerOptions, - setupInstructions: attioSetupInstructions('All Events'), - extraFields: buildAttioExtraFields('attio_webhook'), - }), + subBlocks: buildAttioTriggerSubBlocks('attio_webhook'), outputs: buildGenericWebhookOutputs(), From 23afc8de1cef19c42b496d422a061ebe38dc469c Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 16:46:54 -0800 Subject: [PATCH 12/14] updated docs --- apps/docs/content/docs/en/tools/attio.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/docs/content/docs/en/tools/attio.mdx b/apps/docs/content/docs/en/tools/attio.mdx index d0a76ce9f9..85ad63126b 100644 --- a/apps/docs/content/docs/en/tools/attio.mdx +++ b/apps/docs/content/docs/en/tools/attio.mdx @@ -1012,8 +1012,8 @@ Update a webhook in Attio (target URL and/or subscriptions) | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `webhookId` | string | Yes | The webhook ID to update | -| `targetUrl` | string | No | New HTTPS target URL | -| `subscriptions` | string | No | New JSON array of subscriptions | +| `targetUrl` | string | Yes | HTTPS target URL for webhook delivery | +| `subscriptions` | string | Yes | JSON array of subscriptions, e.g. \[\{"event_type":"note.created"\}\] | #### Output From 1df1933d06eb6ca1f78d8c3bdc4bb4c1b4db1e89 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 16:54:16 -0800 Subject: [PATCH 13/14] fix(attio): remove incorrect save button reference from setup instructions --- apps/sim/triggers/attio/utils.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/apps/sim/triggers/attio/utils.ts b/apps/sim/triggers/attio/utils.ts index 535515bc24..6c60759b42 100644 --- a/apps/sim/triggers/attio/utils.ts +++ b/apps/sim/triggers/attio/utils.ts @@ -26,7 +26,6 @@ export function attioSetupInstructions(): string { const instructions = [ 'Note: Webhooks are automatically created in Attio when you deploy this workflow, and deleted when you undeploy. See the Attio webhook documentation for details.', 'Connect your Attio account using the credential selector above.', - 'Click Save to store your trigger configuration.', 'Deploy the workflow — a webhook will be created automatically in your Attio workspace.', ] From 722d1eb9f2a057af52ed839a307ee1931d1581d0 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 24 Feb 2026 17:27:54 -0800 Subject: [PATCH 14/14] fix(attio): log debug message when signature verification is skipped --- apps/sim/lib/webhooks/processor.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/webhooks/processor.ts b/apps/sim/lib/webhooks/processor.ts index 80cc0147b4..4852ae49fe 100644 --- a/apps/sim/lib/webhooks/processor.ts +++ b/apps/sim/lib/webhooks/processor.ts @@ -601,7 +601,11 @@ export async function verifyProviderAuth( if (foundWebhook.provider === 'attio') { const secret = providerConfig.webhookSecret as string | undefined - if (secret) { + if (!secret) { + logger.debug( + `[${requestId}] Attio webhook ${foundWebhook.id} has no signing secret, skipping signature verification` + ) + } else { const signature = request.headers.get('Attio-Signature') if (!signature) {