Skip to content

Commit d06459f

Browse files
authored
fix(attio): automatic webhook lifecycle management and tool fixes (#3327)
* fix(attio): use code subblock type for JSON input fields * fix(attio): correct people name attribute format in wand prompt example * fix(attio): improve wand prompt with correct attribute formats for all field types * fix(attio): use array format with full_name for personal-name attribute in wand prompt * fix(attio): use loose null checks to prevent sending null params to API * fix(attio): add offset param and make pagination fields advanced mode * fix(attio): remove redundant (optional) from placeholders * fix(attio): always send required workspace_access and workspace_member_access in create list * fix(attio): always send api_slug in create list, auto-generate from name if not provided * fix(attio): update api slug placeholder text * fix(tools): manage lifecycle for attio tools * updated docs * fix(attio): remove incorrect save button reference from setup instructions * fix(attio): log debug message when signature verification is skipped
1 parent 0574427 commit d06459f

37 files changed

+457
-324
lines changed

apps/docs/content/docs/en/tools/attio.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1012,8 +1012,8 @@ Update a webhook in Attio (target URL and/or subscriptions)
10121012
| Parameter | Type | Required | Description |
10131013
| --------- | ---- | -------- | ----------- |
10141014
| `webhookId` | string | Yes | The webhook ID to update |
1015-
| `targetUrl` | string | No | New HTTPS target URL |
1016-
| `subscriptions` | string | No | New JSON array of subscriptions |
1015+
| `targetUrl` | string | Yes | HTTPS target URL for webhook delivery |
1016+
| `subscriptions` | string | Yes | JSON array of subscriptions, e.g. \[\{"event_type":"note.created"\}\] |
10171017

10181018
#### Output
10191019

apps/sim/blocks/blocks/attio.ts

Lines changed: 57 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ export const AttioBlock: BlockConfig<AttioResponse> = {
139139
{
140140
id: 'values',
141141
title: 'Values',
142-
type: 'long-input',
142+
type: 'code',
143143
placeholder: '{"name": "Acme Corp", "domains": [{"domain": "acme.com"}]}',
144144
condition: {
145145
field: 'operation',
@@ -161,21 +161,25 @@ export const AttioBlock: BlockConfig<AttioResponse> = {
161161
Return ONLY the JSON object with Attio attribute values. No explanations, no markdown, no extra text.
162162
163163
### ATTIO VALUES STRUCTURE
164-
Keys are attribute slugs, values follow Attio's attribute format. Simple values can be strings; complex values are arrays of objects.
164+
Keys are attribute slugs. Most text attributes use array-of-objects format [{"value": "..."}]. Special attributes have their own format.
165165
166166
### COMMON PEOPLE ATTRIBUTES
167-
- name: [{"first_name": "...", "last_name": "..."}]
167+
- name: [{"first_name": "...", "last_name": "...", "full_name": "..."}] (personal-name type, full_name is required)
168168
- email_addresses: [{"email_address": "..."}]
169169
- phone_numbers: [{"original_phone_number": "...", "country_code": "US"}]
170-
- job_title, description, linkedin, twitter
170+
- job_title: [{"value": "..."}]
171+
- description: [{"value": "..."}]
172+
- linkedin: [{"value": "https://linkedin.com/in/..."}]
173+
- twitter: [{"value": "@handle"}]
171174
172175
### COMMON COMPANY ATTRIBUTES
173176
- name: [{"value": "..."}]
174177
- domains: [{"domain": "..."}]
175-
- description, primary_location, categories
178+
- description: [{"value": "..."}]
179+
- primary_location: [{"line_1": "...", "locality": "...", "region": "...", "postcode": "...", "country_code": "US"}]
176180
177181
### EXAMPLES
178-
Person: {"name": [{"first_name": "John", "last_name": "Doe"}], "email_addresses": [{"email_address": "john@example.com"}]}
182+
Person: {"name": [{"first_name": "John", "last_name": "Doe", "full_name": "John Doe"}], "email_addresses": [{"email_address": "john@example.com"}], "job_title": [{"value": "Engineer"}]}
179183
Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]}`,
180184
placeholder: 'Describe the record values you want to set...',
181185
generationType: 'json-object',
@@ -184,8 +188,8 @@ Company: {"name": [{"value": "Acme Corp"}], "domains": [{"domain": "acme.com"}]}
184188
{
185189
id: 'filter',
186190
title: 'Filter',
187-
type: 'long-input',
188-
placeholder: '{"name": "John Smith"} (optional)',
191+
type: 'code',
192+
placeholder: '{"name": "John Smith"}',
189193
condition: { field: 'operation', value: 'list_records' },
190194
wandConfig: {
191195
enabled: true,
@@ -215,8 +219,8 @@ Empty (list all): {}`,
215219
{
216220
id: 'sorts',
217221
title: 'Sort',
218-
type: 'long-input',
219-
placeholder: '[{"direction":"asc","attribute":"name"}] (optional)',
222+
type: 'code',
223+
placeholder: '[{"direction":"asc","attribute":"name"}]',
220224
condition: { field: 'operation', value: 'list_records' },
221225
wandConfig: {
222226
enabled: true,
@@ -332,7 +336,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
332336
id: 'noteMeetingId',
333337
title: 'Meeting ID',
334338
type: 'short-input',
335-
placeholder: 'Link to a meeting (optional)',
339+
placeholder: 'Link to a meeting',
336340
condition: { field: 'operation', value: 'create_note' },
337341
mode: 'advanced',
338342
},
@@ -358,7 +362,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
358362
id: 'taskDeadline',
359363
title: 'Deadline',
360364
type: 'short-input',
361-
placeholder: '2024-12-01T15:00:00.000Z (optional)',
365+
placeholder: '2024-12-01T15:00:00.000Z',
362366
condition: { field: 'operation', value: ['create_task', 'update_task'] },
363367
wandConfig: {
364368
enabled: true,
@@ -394,8 +398,8 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
394398
{
395399
id: 'taskLinkedRecords',
396400
title: 'Linked Records',
397-
type: 'long-input',
398-
placeholder: '[{"target_object":"people","target_record_id":"..."}] (optional)',
401+
type: 'code',
402+
placeholder: '[{"target_object":"people","target_record_id":"..."}]',
399403
condition: { field: 'operation', value: ['create_task', 'update_task'] },
400404
wandConfig: {
401405
enabled: true,
@@ -420,9 +424,8 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
420424
{
421425
id: 'taskAssignees',
422426
title: 'Assignees',
423-
type: 'long-input',
424-
placeholder:
425-
'[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}] (optional)',
427+
type: 'code',
428+
placeholder: '[{"referenced_actor_type":"workspace-member","referenced_actor_id":"..."}]',
426429
condition: { field: 'operation', value: ['create_task', 'update_task'] },
427430
wandConfig: {
428431
enabled: true,
@@ -456,21 +459,21 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
456459
id: 'taskFilterObject',
457460
title: 'Linked Object Type',
458461
type: 'short-input',
459-
placeholder: 'e.g. people, companies (optional)',
462+
placeholder: 'e.g. people, companies',
460463
condition: { field: 'operation', value: 'list_tasks' },
461464
},
462465
{
463466
id: 'taskFilterRecordId',
464467
title: 'Linked Record ID',
465468
type: 'short-input',
466-
placeholder: 'Filter by linked record ID (optional)',
469+
placeholder: 'Filter by linked record ID',
467470
condition: { field: 'operation', value: 'list_tasks' },
468471
},
469472
{
470473
id: 'taskFilterAssignee',
471474
title: 'Assignee',
472475
type: 'short-input',
473-
placeholder: 'Filter by assignee email or ID (optional)',
476+
placeholder: 'Filter by assignee email or ID',
474477
condition: { field: 'operation', value: 'list_tasks' },
475478
},
476479
{
@@ -571,7 +574,7 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
571574
id: 'listApiSlug',
572575
title: 'API Slug',
573576
type: 'short-input',
574-
placeholder: 'e.g. my_list (optional, auto-generated)',
577+
placeholder: 'e.g. my_list (auto-generated from name)',
575578
condition: { field: 'operation', value: ['create_list', 'update_list'] },
576579
mode: 'advanced',
577580
},
@@ -622,8 +625,8 @@ Return ONLY the JSON array. No explanations, no markdown, no extra text.
622625
{
623626
id: 'entryValues',
624627
title: 'Entry Values',
625-
type: 'long-input',
626-
placeholder: '{"attribute_slug": "value"} (optional)',
628+
type: 'code',
629+
placeholder: '{"attribute_slug": "value"}',
627630
condition: {
628631
field: 'operation',
629632
value: ['create_list_entry', 'update_list_entry'],
@@ -652,8 +655,8 @@ Keys are list attribute slugs. Values follow Attio attribute format.
652655
{
653656
id: 'entryFilter',
654657
title: 'Filter',
655-
type: 'long-input',
656-
placeholder: '{"attribute": {"$operator": "value"}} (optional)',
658+
type: 'code',
659+
placeholder: '{"attribute": {"$operator": "value"}}',
657660
condition: { field: 'operation', value: 'query_list_entries' },
658661
wandConfig: {
659662
enabled: true,
@@ -679,8 +682,8 @@ Logical: $and, $or, $not
679682
{
680683
id: 'entrySorts',
681684
title: 'Sort',
682-
type: 'long-input',
683-
placeholder: '[{"direction":"asc","attribute":"created_at"}] (optional)',
685+
type: 'code',
686+
placeholder: '[{"direction":"asc","attribute":"created_at"}]',
684687
condition: { field: 'operation', value: 'query_list_entries' },
685688
wandConfig: {
686689
enabled: true,
@@ -819,7 +822,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
819822
id: 'threadFilterRecordId',
820823
title: 'Record ID',
821824
type: 'short-input',
822-
placeholder: 'Filter by record ID (optional)',
825+
placeholder: 'Filter by record ID',
823826
condition: { field: 'operation', value: 'list_threads' },
824827
},
825828
{
@@ -833,7 +836,7 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
833836
id: 'threadFilterEntryId',
834837
title: 'Entry ID',
835838
type: 'short-input',
836-
placeholder: 'Filter by entry ID (optional)',
839+
placeholder: 'Filter by entry ID',
837840
condition: { field: 'operation', value: 'list_threads' },
838841
},
839842
{
@@ -865,15 +868,15 @@ YYYY-MM-DDTHH:mm:ss.SSSZ
865868
type: 'short-input',
866869
placeholder: 'https://example.com/webhook',
867870
condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] },
868-
required: { field: 'operation', value: 'create_webhook' },
871+
required: { field: 'operation', value: ['create_webhook', 'update_webhook'] },
869872
},
870873
{
871874
id: 'webhookSubscriptions',
872875
title: 'Subscriptions',
873-
type: 'long-input',
876+
type: 'code',
874877
placeholder: '[{"event_type":"record.created","filter":{"object_id":"..."}}]',
875878
condition: { field: 'operation', value: ['create_webhook', 'update_webhook'] },
876-
required: { field: 'operation', value: 'create_webhook' },
879+
required: { field: 'operation', value: ['create_webhook', 'update_webhook'] },
877880
wandConfig: {
878881
enabled: true,
879882
maintainHistory: true,
@@ -911,7 +914,8 @@ workspace-member.created
911914
id: 'limit',
912915
title: 'Limit',
913916
type: 'short-input',
914-
placeholder: 'Max results (optional)',
917+
placeholder: 'Max results',
918+
mode: 'advanced',
915919
condition: {
916920
field: 'operation',
917921
value: [
@@ -925,6 +929,24 @@ workspace-member.created
925929
],
926930
},
927931
},
932+
{
933+
id: 'offset',
934+
title: 'Offset',
935+
type: 'short-input',
936+
placeholder: 'Number of results to skip',
937+
mode: 'advanced',
938+
condition: {
939+
field: 'operation',
940+
value: [
941+
'list_records',
942+
'list_notes',
943+
'list_tasks',
944+
'query_list_entries',
945+
'list_threads',
946+
'list_webhooks',
947+
],
948+
},
949+
},
928950
...getTrigger('attio_record_created').subBlocks,
929951
...getTrigger('attio_record_updated').subBlocks,
930952
...getTrigger('attio_record_deleted').subBlocks,
@@ -1103,6 +1125,7 @@ workspace-member.created
11031125

11041126
// Shared params
11051127
if (params.limit) cleanParams.limit = Number(params.limit)
1128+
if (params.offset) cleanParams.offset = Number(params.offset)
11061129

11071130
return cleanParams
11081131
},
@@ -1164,6 +1187,7 @@ workspace-member.created
11641187
webhookTargetUrl: { type: 'string', description: 'Webhook target URL' },
11651188
webhookSubscriptions: { type: 'json', description: 'Webhook event subscriptions' },
11661189
limit: { type: 'string', description: 'Maximum number of results' },
1190+
offset: { type: 'string', description: 'Number of results to skip for pagination' },
11671191
},
11681192

11691193
outputs: {

apps/sim/lib/webhooks/processor.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { convertSquareBracketsToTwiML } from '@/lib/webhooks/utils'
1313
import {
1414
handleSlackChallenge,
1515
handleWhatsAppVerification,
16+
validateAttioSignature,
1617
validateCalcomSignature,
1718
validateCirclebackSignature,
1819
validateFirefliesSignature,
@@ -597,6 +598,33 @@ export async function verifyProviderAuth(
597598
}
598599
}
599600

601+
if (foundWebhook.provider === 'attio') {
602+
const secret = providerConfig.webhookSecret as string | undefined
603+
604+
if (!secret) {
605+
logger.debug(
606+
`[${requestId}] Attio webhook ${foundWebhook.id} has no signing secret, skipping signature verification`
607+
)
608+
} else {
609+
const signature = request.headers.get('Attio-Signature')
610+
611+
if (!signature) {
612+
logger.warn(`[${requestId}] Attio webhook missing signature header`)
613+
return new NextResponse('Unauthorized - Missing Attio signature', { status: 401 })
614+
}
615+
616+
const isValidSignature = validateAttioSignature(secret, signature, rawBody)
617+
618+
if (!isValidSignature) {
619+
logger.warn(`[${requestId}] Attio signature verification failed`, {
620+
signatureLength: signature.length,
621+
secretLength: secret.length,
622+
})
623+
return new NextResponse('Unauthorized - Invalid Attio signature', { status: 401 })
624+
}
625+
}
626+
}
627+
600628
if (foundWebhook.provider === 'linear') {
601629
const secret = providerConfig.webhookSecret as string | undefined
602630

0 commit comments

Comments
 (0)