Skip to content

Commit 1560a39

Browse files
committed
fix(webhooks): subscription recreation path
1 parent b913cff commit 1560a39

3 files changed

Lines changed: 31 additions & 26 deletions

File tree

apps/sim/app/api/webhooks/[id]/route.ts

Lines changed: 10 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
createExternalWebhookSubscription,
1414
shouldRecreateExternalWebhookSubscription,
1515
} from '@/lib/webhooks/provider-subscriptions'
16+
import { mergeNonUserFields } from '@/lib/webhooks/utils'
1617
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1718

1819
const logger = createLogger('WebhookAPI')
@@ -185,21 +186,22 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
185186

186187
const existingProviderConfig =
187188
(webhookData.webhook.providerConfig as Record<string, unknown>) || {}
189+
const nextProvider = (provider ?? webhookData.webhook.provider) as string
190+
188191
let nextProviderConfig =
189192
providerConfig !== undefined &&
190193
resolvedProviderConfig &&
191194
typeof resolvedProviderConfig === 'object'
192195
? (resolvedProviderConfig as Record<string, unknown>)
193196
: existingProviderConfig
194-
const nextProvider = (provider ?? webhookData.webhook.provider) as string
195197

196198
if (
197199
providerConfig !== undefined &&
198200
shouldRecreateExternalWebhookSubscription({
199201
previousProvider: webhookData.webhook.provider as string,
200202
nextProvider,
201203
previousConfig: existingProviderConfig,
202-
nextConfig: nextProviderConfig,
204+
nextConfig: originalProviderConfig as Record<string, unknown>,
203205
})
204206
) {
205207
await cleanupExternalWebhook(
@@ -231,24 +233,13 @@ export async function PATCH(request: NextRequest, { params }: { params: Promise<
231233
hasFailedCountUpdate: failedCount !== undefined,
232234
})
233235

234-
let finalProviderConfig = webhooks[0].webhook.providerConfig
236+
let finalProviderConfig: Record<string, unknown> =
237+
(webhooks[0].webhook.providerConfig as Record<string, unknown>) || {}
235238
if (providerConfig !== undefined && originalProviderConfig) {
236-
const existingConfig = existingProviderConfig
237-
finalProviderConfig = {
238-
...originalProviderConfig,
239-
credentialId: existingConfig.credentialId,
240-
credentialSetId: existingConfig.credentialSetId,
241-
userId: existingConfig.userId,
242-
historyId: existingConfig.historyId,
243-
lastCheckedTimestamp: existingConfig.lastCheckedTimestamp,
244-
setupCompleted: existingConfig.setupCompleted,
245-
externalId: existingConfig.externalId,
246-
}
247-
for (const [key, value] of Object.entries(nextProviderConfig)) {
248-
if (!(key in originalProviderConfig)) {
249-
;(finalProviderConfig as Record<string, unknown>)[key] = value
250-
}
251-
}
239+
const userProvided = originalProviderConfig as Record<string, unknown>
240+
finalProviderConfig = { ...userProvided }
241+
mergeNonUserFields(finalProviderConfig, existingProviderConfig, userProvided)
242+
mergeNonUserFields(finalProviderConfig, nextProviderConfig, userProvided)
252243
}
253244

254245
const updatedWebhook = await db

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

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { PlatformEvents } from '@/lib/core/telemetry'
99
import { generateRequestId } from '@/lib/core/utils/request'
1010
import { resolveEnvVarsInObject } from '@/lib/webhooks/env-resolver'
1111
import { createExternalWebhookSubscription } from '@/lib/webhooks/provider-subscriptions'
12+
import { mergeNonUserFields } from '@/lib/webhooks/utils'
1213
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1314

1415
const logger = createLogger('WebhooksAPI')
@@ -466,7 +467,8 @@ export async function POST(request: NextRequest) {
466467
providerConfig: providerConfigOverride,
467468
})
468469

469-
const configToSave = { ...originalProviderConfig }
470+
const userProvided = originalProviderConfig as Record<string, unknown>
471+
const configToSave: Record<string, unknown> = { ...userProvided }
470472

471473
try {
472474
const result = await createExternalWebhookSubscription(
@@ -477,11 +479,7 @@ export async function POST(request: NextRequest) {
477479
requestId
478480
)
479481
const updatedConfig = result.updatedProviderConfig as Record<string, unknown>
480-
for (const [key, value] of Object.entries(updatedConfig)) {
481-
if (!(key in originalProviderConfig)) {
482-
configToSave[key] = value
483-
}
484-
}
482+
mergeNonUserFields(configToSave, updatedConfig, userProvided)
485483
resolvedProviderConfig = updatedConfig
486484
externalSubscriptionCreated = result.externalSubscriptionCreated
487485
} catch (err) {

apps/sim/lib/webhooks/utils.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Pure utility functions for TwiML processing
2+
* Pure utility functions for webhook processing
33
* This file has NO server-side dependencies to ensure it can be safely imported in client-side code
44
*/
55

@@ -18,3 +18,19 @@ export function convertSquareBracketsToTwiML(twiml: string | undefined): string
1818
// Replace [Tag] with <Tag> and [/Tag] with </Tag>
1919
return twiml.replace(/\[(\/?[^\]]+)\]/g, '<$1>')
2020
}
21+
22+
/**
23+
* Merges fields from source into target, but only if they don't exist in the base config.
24+
* Used to preserve system-managed fields while respecting user-provided values.
25+
*/
26+
export function mergeNonUserFields(
27+
target: Record<string, unknown>,
28+
source: Record<string, unknown>,
29+
userProvided: Record<string, unknown>
30+
): void {
31+
for (const [key, value] of Object.entries(source)) {
32+
if (!(key in userProvided)) {
33+
target[key] = value
34+
}
35+
}
36+
}

0 commit comments

Comments
 (0)