Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 4 additions & 1 deletion apps/sim/app/api/webhooks/trigger/[path]/route.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
ADMISSION_ERROR_DESCRIPTOR,
ADMISSION_RETRY_AFTER_SECONDS,
} from '@/lib/core/admission/transient-failure'
import { INTERNAL_TRIGGER_PROVIDERS, POLLING_PROVIDERS } from '@/triggers/constants'

vi.mock('@/lib/core/security/encryption', () => encryptionMock)

Expand Down Expand Up @@ -638,7 +639,8 @@ describe('Webhook Trigger API Route', () => {
})

describe('Non-path trigger providers', () => {
it.each(['sim', 'table', 'tiktok'])(
/** Sourced from the registries so a newly added trigger is covered automatically. */
it.each([...INTERNAL_TRIGGER_PROVIDERS, ...POLLING_PROVIDERS, 'tiktok'])(
'rejects HTTP deliveries to %s trigger paths with 404',
async (provider) => {
testData.webhooks.push({
Expand All @@ -657,6 +659,7 @@ describe('Webhook Trigger API Route', () => {

expect(response.status).toBe(404)
expect(queueWebhookExecutionMock).not.toHaveBeenCalled()
expect(dispatchResolvedWebhookTargetMock).not.toHaveBeenCalled()
}
)

Expand Down
8 changes: 2 additions & 6 deletions apps/sim/app/api/webhooks/trigger/[path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ import {
verifyProviderAuth,
} from '@/lib/webhooks/processor'
import { acceptsPathWebhookDelivery } from '@/lib/webhooks/providers'
import { isInternalTriggerProvider } from '@/triggers/constants'

const logger = createLogger('WebhookTriggerAPI')

Expand Down Expand Up @@ -100,11 +99,8 @@ async function handleWebhookPost(
// Find all webhooks for this path (multiple webhooks in one workflow may share a path)
const allWebhooksForPath = await findAllWebhooksForPath({ requestId, path })

/** Exclude in-process triggers and providers that own an app-level ingress route. */
const webhooksForPath = allWebhooksForPath.filter(
({ webhook: foundWebhook }) =>
!isInternalTriggerProvider(foundWebhook.provider) &&
acceptsPathWebhookDelivery(foundWebhook.provider)
const webhooksForPath = allWebhooksForPath.filter(({ webhook: foundWebhook }) =>
acceptsPathWebhookDelivery(foundWebhook.provider)
)

if (allWebhooksForPath.length > 0 && webhooksForPath.length === 0) {
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/ee/workspace-forking/lib/copy/deploy-bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ export async function loadTargetWebhookPathsByBlock(
const byBlock = new Map<string, ForkTargetWebhook>()
for (const row of rows) {
if (!row.blockId || !row.path) continue
if (isPollingWebhookProvider(row.provider ?? '') || isInternalTriggerProvider(row.provider)) {
if (isPollingWebhookProvider(row.provider) || isInternalTriggerProvider(row.provider)) {
continue
}
// One live path-based row per block within a version - `path_deployment_unique` enforces it.
Expand Down
11 changes: 10 additions & 1 deletion apps/sim/lib/webhooks/providers/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { getProviderHandler } from '@/lib/webhooks/providers/registry'

import { getProviderHandler } from '@/lib/webhooks/providers/registry'
import { isInternalTriggerProvider, isPollingWebhookProvider } from '@/triggers/constants'

/**
* Extract a provider-specific unique identifier from the webhook body for idempotency.
Expand All @@ -14,8 +15,16 @@ export function extractProviderIdentifierFromBody(provider: string, body: unknow
return handler.extractIdempotencyId?.(body) ?? null
}

/** Returns whether a provider accepts deliveries through the generic per-webhook path route. */
/**
* Whether a provider accepts deliveries through the generic per-webhook path route.
*
* False for triggers Sim fires itself - internal (table row, workspace events) and polling
* (pulled from `/api/webhooks/poll/[provider]`) - and for providers that own an app-level
* ingress route: their rows still register a path but declare no `verifyAuth`, so anyone
* holding the block ID could otherwise forge events.
*/
export function acceptsPathWebhookDelivery(provider: string | null): boolean {
if (!provider) return true
if (isInternalTriggerProvider(provider) || isPollingWebhookProvider(provider)) return false
return getProviderHandler(provider).ingressMode !== 'provider'
}
4 changes: 2 additions & 2 deletions apps/sim/triggers/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ export const POLLING_PROVIDERS = new Set([
'rss',
])

export function isPollingWebhookProvider(provider: string): boolean {
return POLLING_PROVIDERS.has(provider)
export function isPollingWebhookProvider(provider: string | null): boolean {
return provider !== null && POLLING_PROVIDERS.has(provider)
}

/**
Expand Down
Loading