Skip to content

Commit d887612

Browse files
authored
fix(webhooks): answer missing trigger configuration with 400 and stop logging refused resumes as errors (#9659)
* fix(webhooks): answer missing trigger configuration with 400 and log refused resumes at warn * fix(webhooks): type blank Plane tokens and non-string Airtable IDs as config errors, dedupe resume refusal logs
1 parent 965f336 commit d887612

33 files changed

Lines changed: 171 additions & 71 deletions

‎apps/sim/app/api/webhooks/route.ts‎

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
shouldRecreateExternalWebhookSubscription,
3434
} from '@/lib/webhooks/provider-subscriptions'
3535
import { getProviderHandler } from '@/lib/webhooks/providers'
36+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
3637
import { mergeNonUserFields } from '@/lib/webhooks/utils'
3738
import { findConflictingWebhookPathOwner } from '@/lib/webhooks/utils.server'
3839
import { listAccessibleWorkspaceRowsForUser } from '@/lib/workspaces/utils'
@@ -573,13 +574,20 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
573574
}
574575
}
575576
} catch (err) {
576-
logger.error(`[${requestId}] Error creating external webhook subscription`, err)
577+
const isConfigurationError = err instanceof WebhookDeploymentConfigurationError
578+
if (isConfigurationError) {
579+
logger.warn(`[${requestId}] Rejected webhook trigger configuration`, {
580+
error: err.message,
581+
})
582+
} else {
583+
logger.error(`[${requestId}] Error creating external webhook subscription`, err)
584+
}
577585
return NextResponse.json(
578586
{
579587
error: 'Failed to create external webhook subscription',
580588
details: getErrorMessage(err, 'Unknown error'),
581589
},
582-
{ status: 500 }
590+
{ status: isConfigurationError ? 400 : 500 }
583591
)
584592
}
585593
} else {

‎apps/sim/lib/webhooks/providers/airtable.ts‎

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { db } from '@sim/db'
22
import { account, webhook } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
4+
import { toStringOrNull } from '@sim/utils/coerce'
45
import { toRecord } from '@sim/utils/object'
56
import { eq } from 'drizzle-orm'
67
import { validateAirtableId } from '@/lib/core/security/input-validation'
@@ -14,6 +15,7 @@ import {
1415
getNotificationUrl,
1516
getProviderConfig,
1617
} from '@/lib/webhooks/provider-subscription-utils'
18+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
1719
import type {
1820
DeleteSubscriptionContext,
1921
FormatInputContext,
@@ -435,30 +437,30 @@ export const airtableHandler: WebhookProviderHandler = {
435437
}: SubscriptionContext): Promise<SubscriptionResult | undefined> {
436438
try {
437439
const config = getProviderConfig(webhookRecord)
438-
const { baseId, tableId, includeCellValuesInFieldIds, credentialId } = config as {
439-
baseId?: string
440-
tableId?: string
440+
const { includeCellValuesInFieldIds, credentialId } = config as {
441441
includeCellValuesInFieldIds?: string
442442
credentialId?: string
443443
}
444+
const baseId = toStringOrNull(config.baseId)
445+
const tableId = toStringOrNull(config.tableId)
444446

445447
if (!baseId || !tableId) {
446448
logger.warn(`[${requestId}] Missing baseId or tableId for Airtable webhook creation.`, {
447449
webhookId: webhookRecord.id,
448450
})
449-
throw new Error(
451+
throw new WebhookDeploymentConfigurationError(
450452
'Base ID and Table ID are required to create Airtable webhook. Please provide valid Airtable base and table IDs.'
451453
)
452454
}
453455

454456
const baseIdValidation = validateAirtableId(baseId, 'app', 'baseId')
455457
if (!baseIdValidation.isValid) {
456-
throw new Error(baseIdValidation.error)
458+
throw new WebhookDeploymentConfigurationError(baseIdValidation.error)
457459
}
458460

459461
const tableIdValidation = validateAirtableId(tableId, 'tbl', 'tableId')
460462
if (!tableIdValidation.isValid) {
461-
throw new Error(tableIdValidation.error)
463+
throw new WebhookDeploymentConfigurationError(tableIdValidation.error)
462464
}
463465

464466
const accessToken = credentialId
@@ -535,6 +537,7 @@ export const airtableHandler: WebhookProviderHandler = {
535537
)
536538
return { providerConfigUpdates: { externalId: responseBody.id } }
537539
} catch (error: unknown) {
540+
if (error instanceof WebhookDeploymentConfigurationError) throw error
538541
const err = error as Error
539542
logger.error(
540543
`[${requestId}] Exception during Airtable webhook creation for webhook ${webhookRecord.id}.`,

‎apps/sim/lib/webhooks/providers/ashby.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { isRecordLike, omit, toRecord } from '@sim/utils/object'
66
import { NextResponse } from 'next/server'
77
import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits'
88
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
9+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
910
import type {
1011
AuthContext,
1112
DeleteSubscriptionContext,
@@ -235,13 +236,15 @@ export const ashbyHandler: WebhookProviderHandler = {
235236
}
236237

237238
if (!apiKey) {
238-
throw new Error(
239+
throw new WebhookDeploymentConfigurationError(
239240
'Ashby API Key is required. Please provide your API Key with apiKeysWrite permission in the trigger configuration.'
240241
)
241242
}
242243

243244
if (!triggerId) {
244-
throw new Error('Trigger ID is required to create Ashby webhook.')
245+
throw new WebhookDeploymentConfigurationError(
246+
'Trigger ID is required to create Ashby webhook.'
247+
)
245248
}
246249

247250
const { ASHBY_TRIGGER_ACTION_MAP } = await import('@/triggers/ashby/utils')
@@ -322,6 +325,7 @@ export const ashbyHandler: WebhookProviderHandler = {
322325
)
323326
return { providerConfigUpdates: { externalId, secretToken } }
324327
} catch (error: unknown) {
328+
if (error instanceof WebhookDeploymentConfigurationError) throw error
325329
const err = error as Error
326330
logger.error(
327331
`[${ctx.requestId}] Exception during Ashby webhook creation for webhook ${ctx.webhook.id}.`,

‎apps/sim/lib/webhooks/providers/attio.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getNotificationUrl,
1010
getProviderConfig,
1111
} from '@/lib/webhooks/provider-subscription-utils'
12+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
1213
import type {
1314
AuthContext,
1415
DeleteSubscriptionContext,
@@ -116,7 +117,7 @@ export const attioHandler: WebhookProviderHandler = {
116117
logger.warn(`[${requestId}] Missing credentialId for Attio webhook creation.`, {
117118
webhookId: webhookRecord.id,
118119
})
119-
throw new Error(
120+
throw new WebhookDeploymentConfigurationError(
120121
'Attio account connection required. Please connect your Attio account in the trigger configuration and try again.'
121122
)
122123
}
@@ -222,6 +223,7 @@ export const attioHandler: WebhookProviderHandler = {
222223

223224
return { providerConfigUpdates: { externalId: webhookId, webhookSecret: secret || '' } }
224225
} catch (error: unknown) {
226+
if (error instanceof WebhookDeploymentConfigurationError) throw error
225227
const message = toError(error).message
226228
logger.error(
227229
`[${requestId}] Exception during Attio webhook creation for webhook ${webhookRecord.id}.`,

‎apps/sim/lib/webhooks/providers/bitbucket.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
getNotificationUrl,
1212
getProviderConfig,
1313
} from '@/lib/webhooks/provider-subscription-utils'
14+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
1415
import type {
1516
DeleteSubscriptionContext,
1617
EventMatchContext,
@@ -89,7 +90,7 @@ async function resolveBitbucketAccessToken(
8990
requestId: string
9091
): Promise<string> {
9192
if (!credentialId) {
92-
throw new Error(
93+
throw new WebhookDeploymentConfigurationError(
9394
'Bitbucket account connection required. Connect or reconnect Bitbucket in the trigger configuration and try again.'
9495
)
9596
}
@@ -112,7 +113,9 @@ function readRequiredConfigString(
112113
): string {
113114
const value = config[key]
114115
if (typeof value !== 'string' || !value.trim()) {
115-
throw new Error(`Bitbucket ${label} is required to manage the repository webhook.`)
116+
throw new WebhookDeploymentConfigurationError(
117+
`Bitbucket ${label} is required to manage the repository webhook.`
118+
)
116119
}
117120
return value.trim()
118121
}

‎apps/sim/lib/webhooks/providers/calendly.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { createLogger } from '@sim/logger'
22
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
3+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
34
import type {
45
DeleteSubscriptionContext,
56
FormatInputContext,
@@ -37,7 +38,7 @@ export const calendlyHandler: WebhookProviderHandler = {
3738
logger.warn(`[${ctx.requestId}] Missing apiKey for Calendly webhook creation.`, {
3839
webhookId: ctx.webhook.id,
3940
})
40-
throw new Error(
41+
throw new WebhookDeploymentConfigurationError(
4142
'Personal Access Token is required to create Calendly webhook. Please provide your Calendly Personal Access Token.'
4243
)
4344
}
@@ -46,7 +47,7 @@ export const calendlyHandler: WebhookProviderHandler = {
4647
logger.warn(`[${ctx.requestId}] Missing organization URI for Calendly webhook creation.`, {
4748
webhookId: ctx.webhook.id,
4849
})
49-
throw new Error(
50+
throw new WebhookDeploymentConfigurationError(
5051
'Organization URI is required to create Calendly webhook. Please provide your Organization URI from the "Get Current User" operation.'
5152
)
5253
}
@@ -55,7 +56,9 @@ export const calendlyHandler: WebhookProviderHandler = {
5556
logger.warn(`[${ctx.requestId}] Missing triggerId for Calendly webhook creation.`, {
5657
webhookId: ctx.webhook.id,
5758
})
58-
throw new Error('Trigger ID is required to create Calendly webhook')
59+
throw new WebhookDeploymentConfigurationError(
60+
'Trigger ID is required to create Calendly webhook'
61+
)
5962
}
6063

6164
const notificationUrl = getNotificationUrl(ctx.webhook)
@@ -152,6 +155,7 @@ export const calendlyHandler: WebhookProviderHandler = {
152155
)
153156
return { providerConfigUpdates: { externalId: webhookId } }
154157
} catch (error: unknown) {
158+
if (error instanceof WebhookDeploymentConfigurationError) throw error
155159
const err = error as Error
156160
logger.error(
157161
`[${ctx.requestId}] Exception during Calendly webhook creation for webhook ${ctx.webhook.id}.`,

‎apps/sim/lib/webhooks/providers/checkr.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { and, eq, isNull, ne } from 'drizzle-orm'
88
import { NextResponse } from 'next/server'
99
import { isPayloadSizeLimitError, readResponseJsonWithLimit } from '@/lib/core/utils/stream-limits'
1010
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
11+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
1112
import type {
1213
AuthContext,
1314
DeleteSubscriptionContext,
@@ -198,7 +199,9 @@ export const checkrHandler: WebhookProviderHandler = {
198199
const providerConfig = getProviderConfig(ctx.webhook)
199200
const apiKey = typeof providerConfig.apiKey === 'string' ? providerConfig.apiKey.trim() : ''
200201
if (!apiKey) {
201-
throw new Error('Checkr API key is required to register the webhook.')
202+
throw new WebhookDeploymentConfigurationError(
203+
'Checkr API key is required to register the webhook.'
204+
)
202205
}
203206

204207
const notificationUrl = getNotificationUrl(ctx.webhook)

‎apps/sim/lib/webhooks/providers/clickup.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
getNotificationUrl,
1010
getProviderConfig,
1111
} from '@/lib/webhooks/provider-subscription-utils'
12+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
1213
import type {
1314
DeleteSubscriptionContext,
1415
EventMatchContext,
@@ -46,7 +47,7 @@ async function resolveClickUpAccessToken(
4647
requestId: string
4748
): Promise<string> {
4849
if (!credentialId) {
49-
throw new Error(
50+
throw new WebhookDeploymentConfigurationError(
5051
'ClickUp account connection required. Please connect your ClickUp account in the trigger configuration and try again.'
5152
)
5253
}
@@ -72,7 +73,9 @@ function parseOptionalNumericId(value: unknown, label: string): number | undefin
7273
if (!raw) return undefined
7374
const parsed = Number(raw)
7475
if (!Number.isInteger(parsed)) {
75-
throw new Error(`ClickUp ${label} must be a whole number. Received: ${raw}`)
76+
throw new WebhookDeploymentConfigurationError(
77+
`ClickUp ${label} must be a whole number. Received: ${raw}`
78+
)
7679
}
7780
return parsed
7881
}
@@ -154,7 +157,7 @@ export const clickupHandler: WebhookProviderHandler = {
154157

155158
const workspaceId = parseOptionalStringId(config.triggerWorkspaceId)
156159
if (!workspaceId) {
157-
throw new Error(
160+
throw new WebhookDeploymentConfigurationError(
158161
'ClickUp workspace is required. Please select a workspace in the trigger configuration and try again.'
159162
)
160163
}
@@ -271,6 +274,7 @@ export const clickupHandler: WebhookProviderHandler = {
271274

272275
return { providerConfigUpdates: { externalId, webhookSecret: secret } }
273276
} catch (error: unknown) {
277+
if (error instanceof WebhookDeploymentConfigurationError) throw error
274278
const message = toError(error).message
275279
logger.error(
276280
`[${requestId}] Exception during ClickUp webhook creation for webhook ${webhookRecord.id}.`,

‎apps/sim/lib/webhooks/providers/emailbison.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
validateUrlWithDNS,
88
} from '@/lib/core/security/input-validation.server'
99
import { getNotificationUrl, getProviderConfig } from '@/lib/webhooks/provider-subscription-utils'
10+
import { WebhookDeploymentConfigurationError } from '@/lib/webhooks/providers/errors'
1011
import type {
1112
DeleteSubscriptionContext,
1213
EventMatchContext,
@@ -127,15 +128,15 @@ export const emailBisonHandler: WebhookProviderHandler = {
127128
const triggerId = providerConfig.triggerId as string | undefined
128129

129130
if (!apiKey?.trim()) {
130-
throw new Error('Email Bison API Key is required.')
131+
throw new WebhookDeploymentConfigurationError('Email Bison API Key is required.')
131132
}
132133

133134
if (!apiBaseUrl?.trim()) {
134-
throw new Error('Email Bison Instance URL is required.')
135+
throw new WebhookDeploymentConfigurationError('Email Bison Instance URL is required.')
135136
}
136137

137138
if (!triggerId) {
138-
throw new Error('Email Bison trigger ID is required.')
139+
throw new WebhookDeploymentConfigurationError('Email Bison trigger ID is required.')
139140
}
140141

141142
const { getEmailBisonEventTypeForTrigger } = await import('@/triggers/emailbison/utils')

‎apps/sim/lib/webhooks/providers/errors.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
export class WebhookDeploymentConfigurationError extends Error {
2-
constructor(message: string) {
2+
constructor(message?: string) {
33
super(message)
44
this.name = 'WebhookDeploymentConfigurationError'
55
}

0 commit comments

Comments
 (0)