Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2e89fe5
fix(triggers): apply webhook audit follow-ups
waleedlatif1 Apr 6, 2026
bb716bb
fix(webhooks): Salesforce provider handler, Zoom CRC and block wiring
waleedlatif1 Apr 6, 2026
0ddc769
fix(webhooks): harden Resend and Linear triggers (idempotency, auth, …
waleedlatif1 Apr 6, 2026
e9618d9
fix(webhooks): harden Vercel and Greenhouse trigger handlers
waleedlatif1 Apr 6, 2026
317d4ab
fix(gong): JWT verification, trigger UX, alignment script
waleedlatif1 Apr 6, 2026
729667a
fix(notion): align webhook lifecycle and outputs
waleedlatif1 Apr 6, 2026
e79c556
fix(webhooks): tighten remaining provider hardening
waleedlatif1 Apr 7, 2026
23ccc9b
refactor(webhooks): move subscription helpers out of providers
waleedlatif1 Apr 7, 2026
e000c5b
fix(zoom): resolve env-backed secrets during validation
waleedlatif1 Apr 7, 2026
b50a902
fix build
waleedlatif1 Apr 7, 2026
732755a
consolidate tests
waleedlatif1 Apr 7, 2026
7c31044
refactor(salesforce): share payload object type parsing
waleedlatif1 Apr 7, 2026
41b0348
fix(webhooks): address remaining review follow-ups
waleedlatif1 Apr 7, 2026
cae9c8b
test(webhooks): separate Zoom coverage and clean Notion output shape
waleedlatif1 Apr 7, 2026
a305fc2
feat(triggers): enrich Vercel and Greenhouse webhook output shapes
waleedlatif1 Apr 7, 2026
3e29341
feat(webhooks): enrich Resend trigger outputs; clarify Notion output …
waleedlatif1 Apr 7, 2026
5148936
feat(webhooks): enrich Zoom and Gong trigger output schemas
waleedlatif1 Apr 7, 2026
0600c90
feat(triggers): enrich Salesforce and Linear webhook output schemas
waleedlatif1 Apr 7, 2026
1cd27d8
remove from mdx
waleedlatif1 Apr 7, 2026
e0580f7
chore(webhooks): expand trigger alignment coverage
waleedlatif1 Apr 7, 2026
3e7a046
updated skills
waleedlatif1 Apr 7, 2026
d3fcf04
updated file naming semantics
waleedlatif1 Apr 7, 2026
2897ce1
rename file
waleedlatif1 Apr 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
feat(triggers): enrich Salesforce and Linear webhook output schemas
Salesforce: expose simEventType alongside eventType; pass OwnerId and
SystemModstamp on record lifecycle inputs; add AccountId/OwnerId for
Opportunity and AccountId/ContactId/OwnerId for Case. Align trigger
output docs with Flow JSON payloads and formatInput.

Linear: document actor email and profile url per official webhook
payload; add Comment data.edited from Linear's sample payload.

Tests: extend Salesforce formatInput coverage for new fields.
  • Loading branch information
waleedlatif1 committed Apr 7, 2026
commit 0600c90f1cdcc25d761b02d5cc1bb30adb13c656
7 changes: 7 additions & 0 deletions apps/sim/lib/webhooks/providers/salesforce.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,12 @@ describe('Salesforce webhook provider', () => {
const { input } = await salesforceHandler.formatInput!({
body: {
eventType: 'created',
simEventType: 'after_insert',
objectType: 'Lead',
Id: '00Q1',
Name: 'Test',
OwnerId: '005OWNER',
SystemModstamp: '2024-01-01T00:00:00.000Z',
},
headers: {},
requestId: 't4',
Expand All @@ -89,8 +92,12 @@ describe('Salesforce webhook provider', () => {
})
const i = input as Record<string, unknown>
expect(i.eventType).toBe('created')
expect(i.simEventType).toBe('after_insert')
expect(i.objectType).toBe('Lead')
expect(i.recordId).toBe('00Q1')
const rec = i.record as Record<string, unknown>
expect(rec.OwnerId).toBe('005OWNER')
expect(rec.SystemModstamp).toBe('2024-01-01T00:00:00.000Z')
})

it('extractIdempotencyId includes record id', () => {
Expand Down
18 changes: 18 additions & 0 deletions apps/sim/lib/webhooks/providers/salesforce.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ function pickRecordId(body: Record<string, unknown>, record: Record<string, unkn
return id
}

function pickStr(record: Record<string, unknown>, key: string): string {
const v = record[key]
return typeof v === 'string' ? v : ''
}

export const salesforceHandler: WebhookProviderHandler = {
verifyAuth({ request, requestId, providerConfig }: AuthContext): NextResponse | null {
const secret = providerConfig.webhookSecret as string | undefined
Expand Down Expand Up @@ -157,6 +162,7 @@ export const salesforceHandler: WebhookProviderHandler = {
(typeof body.eventType === 'string' && body.eventType) ||
(typeof body.simEventType === 'string' && body.simEventType) ||
''
const simEventTypeRaw = typeof body.simEventType === 'string' ? body.simEventType : ''

if (id === 'salesforce_webhook') {
return {
Expand All @@ -165,6 +171,7 @@ export const salesforceHandler: WebhookProviderHandler = {
objectType: objectType || '',
recordId,
timestamp,
simEventType: simEventTypeRaw,
record: Object.keys(record).length > 0 ? record : body,
payload: ctx.body,
},
Expand All @@ -183,12 +190,15 @@ export const salesforceHandler: WebhookProviderHandler = {
objectType: objectType || '',
recordId,
timestamp,
simEventType: simEventTypeRaw,
record: {
Id: typeof record.Id === 'string' ? record.Id : recordId,
Name: typeof record.Name === 'string' ? record.Name : '',
CreatedDate: typeof record.CreatedDate === 'string' ? record.CreatedDate : '',
LastModifiedDate:
typeof record.LastModifiedDate === 'string' ? record.LastModifiedDate : '',
OwnerId: pickStr(record, 'OwnerId'),
SystemModstamp: pickStr(record, 'SystemModstamp'),
},
changedFields: changedFields !== undefined ? changedFields : null,
payload: ctx.body,
Expand All @@ -203,13 +213,16 @@ export const salesforceHandler: WebhookProviderHandler = {
objectType: objectType || 'Opportunity',
recordId,
timestamp,
simEventType: simEventTypeRaw,
record: {
Id: typeof record.Id === 'string' ? record.Id : recordId,
Name: typeof record.Name === 'string' ? record.Name : '',
StageName: typeof record.StageName === 'string' ? record.StageName : '',
Amount: record.Amount !== undefined ? String(record.Amount) : '',
CloseDate: typeof record.CloseDate === 'string' ? record.CloseDate : '',
Probability: record.Probability !== undefined ? String(record.Probability) : '',
AccountId: pickStr(record, 'AccountId'),
OwnerId: pickStr(record, 'OwnerId'),
},
previousStage:
typeof body.previousStage === 'string'
Expand All @@ -235,12 +248,16 @@ export const salesforceHandler: WebhookProviderHandler = {
objectType: objectType || 'Case',
recordId,
timestamp,
simEventType: simEventTypeRaw,
record: {
Id: typeof record.Id === 'string' ? record.Id : recordId,
Subject: typeof record.Subject === 'string' ? record.Subject : '',
Status: typeof record.Status === 'string' ? record.Status : '',
Priority: typeof record.Priority === 'string' ? record.Priority : '',
CaseNumber: typeof record.CaseNumber === 'string' ? record.CaseNumber : '',
AccountId: pickStr(record, 'AccountId'),
ContactId: pickStr(record, 'ContactId'),
OwnerId: pickStr(record, 'OwnerId'),
},
previousStatus:
typeof body.previousStatus === 'string'
Expand All @@ -265,6 +282,7 @@ export const salesforceHandler: WebhookProviderHandler = {
objectType: objectType || '',
recordId,
timestamp,
simEventType: simEventTypeRaw,
record: Object.keys(record).length > 0 ? record : body,
payload: ctx.body,
},
Expand Down
16 changes: 14 additions & 2 deletions apps/sim/triggers/linear/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ export function buildLinearV2SubBlocks(options: {
}

/**
* Shared user/actor output schema
* Note: Linear webhooks only include id, name, and type in actor objects
* Shared user/actor output schema (Linear data-change webhook `actor` object).
* @see https://linear.app/developers/webhooks — actor may be a User, OauthClient, or Integration; `type` is mapped to `actorType` (TriggerOutput reserves nested `type` for field kinds).
*/
export const userOutputs = {
id: {
Expand All @@ -211,6 +211,14 @@ export const userOutputs = {
type: 'string',
description: 'Actor type from Linear (e.g. user, OauthClient, Integration)',
},
email: {
type: 'string',
description: 'Actor email (present for user actors in Linear webhook payloads)',
},
url: {
type: 'string',
description: 'Actor profile URL in Linear (distinct from the top-level subject entity `url`)',
},
} as const

/**
Expand Down Expand Up @@ -495,6 +503,10 @@ export function buildCommentOutputs(): Record<string, TriggerOutput> {
type: 'string',
description: 'Comment body text',
},
edited: {
type: 'boolean',
description: 'Whether the comment body has been edited (Linear webhook payload field)',
},
url: {
type: 'string',
description: 'Comment URL',
Expand Down
34 changes: 34 additions & 0 deletions apps/sim/triggers/salesforce/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,12 @@ export function buildSalesforceRecordOutputs(): Record<string, TriggerOutput> {
type: 'string',
description: 'The type of event (e.g., created, updated, deleted)',
},
/** Present when the Flow JSON body uses `simEventType` instead of or in addition to `eventType`. */
simEventType: {
type: 'string',
description:
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
},
objectType: {
type: 'string',
description: 'Salesforce object type (e.g., Account, Contact, Lead)',
Expand All @@ -245,6 +251,14 @@ export function buildSalesforceRecordOutputs(): Record<string, TriggerOutput> {
Name: { type: 'string', description: 'Record name' },
CreatedDate: { type: 'string', description: 'Record creation date' },
LastModifiedDate: { type: 'string', description: 'Last modification date' },
OwnerId: {
type: 'string',
description: 'Record owner ID (standard field when sent in the Flow body)',
},
SystemModstamp: {
type: 'string',
description: 'System modstamp from the record (ISO 8601) when included in the payload',
},
},
changedFields: { type: 'json', description: 'Fields that were changed (for update events)' },
payload: { type: 'json', description: 'Full webhook payload' },
Expand All @@ -257,6 +271,11 @@ export function buildSalesforceRecordOutputs(): Record<string, TriggerOutput> {
export function buildSalesforceOpportunityStageOutputs(): Record<string, TriggerOutput> {
return {
eventType: { type: 'string', description: 'The type of event' },
simEventType: {
type: 'string',
description:
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
},
objectType: { type: 'string', description: 'Salesforce object type (Opportunity)' },
recordId: { type: 'string', description: 'Opportunity ID' },
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
Expand All @@ -267,6 +286,8 @@ export function buildSalesforceOpportunityStageOutputs(): Record<string, Trigger
Amount: { type: 'string', description: 'Deal amount' },
CloseDate: { type: 'string', description: 'Expected close date' },
Probability: { type: 'string', description: 'Win probability' },
AccountId: { type: 'string', description: 'Related Account ID (standard Opportunity field)' },
OwnerId: { type: 'string', description: 'Opportunity owner ID' },
},
previousStage: { type: 'string', description: 'Previous stage name' },
newStage: { type: 'string', description: 'New stage name' },
Expand All @@ -280,6 +301,11 @@ export function buildSalesforceOpportunityStageOutputs(): Record<string, Trigger
export function buildSalesforceCaseStatusOutputs(): Record<string, TriggerOutput> {
return {
eventType: { type: 'string', description: 'The type of event' },
simEventType: {
type: 'string',
description:
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
},
objectType: { type: 'string', description: 'Salesforce object type (Case)' },
recordId: { type: 'string', description: 'Case ID' },
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
Expand All @@ -289,6 +315,9 @@ export function buildSalesforceCaseStatusOutputs(): Record<string, TriggerOutput
Status: { type: 'string', description: 'Current case status' },
Priority: { type: 'string', description: 'Case priority' },
CaseNumber: { type: 'string', description: 'Case number' },
AccountId: { type: 'string', description: 'Related Account ID' },
ContactId: { type: 'string', description: 'Related Contact ID' },
OwnerId: { type: 'string', description: 'Case owner ID' },
},
previousStatus: { type: 'string', description: 'Previous case status' },
newStatus: { type: 'string', description: 'New case status' },
Expand All @@ -302,6 +331,11 @@ export function buildSalesforceCaseStatusOutputs(): Record<string, TriggerOutput
export function buildSalesforceWebhookOutputs(): Record<string, TriggerOutput> {
return {
eventType: { type: 'string', description: 'The type of event' },
simEventType: {
type: 'string',
description:
'Optional alias from the payload (`simEventType`). Empty when only `eventType` is sent.',
},
objectType: { type: 'string', description: 'Salesforce object type' },
recordId: { type: 'string', description: 'ID of the affected record' },
timestamp: { type: 'string', description: 'When the event occurred (ISO 8601)' },
Expand Down
Loading