Skip to content

Commit 3f7cab4

Browse files
committed
consolidate webhook code better
1 parent cd0a08b commit 3f7cab4

2 files changed

Lines changed: 83 additions & 84 deletions

File tree

apps/sim/app/api/webhooks/trigger/[path]/route.ts

Lines changed: 17 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@ import { generateRequestId } from '@/lib/core/utils/request'
44
import {
55
checkWebhookPreprocessing,
66
findAllWebhooksForPath,
7+
formatProviderErrorResponse,
8+
handlePreDeploymentVerification,
79
handleProviderChallenges,
810
handleProviderReachabilityTest,
911
parseWebhookBody,
1012
queueWebhookExecution,
13+
shouldSkipWebhookEvent,
1114
verifyProviderAuth,
1215
} from '@/lib/webhooks/processor'
1316
import { blockExistsInDeployment } from '@/lib/workflows/persistence/utils'
@@ -22,19 +25,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
2225
const requestId = generateRequestId()
2326
const { path } = await params
2427

25-
// Handle Microsoft Graph subscription validation
26-
const url = new URL(request.url)
27-
const validationToken = url.searchParams.get('validationToken')
28-
29-
if (validationToken) {
30-
logger.info(`[${requestId}] Microsoft Graph subscription validation for path: ${path}`)
31-
return new NextResponse(validationToken, {
32-
status: 200,
33-
headers: { 'Content-Type': 'text/plain' },
34-
})
35-
}
36-
37-
// Handle other GET-based verifications if needed
28+
// Handle provider-specific GET verifications (Microsoft Graph, WhatsApp, etc.)
3829
const challengeResponse = await handleProviderChallenges({}, request, requestId, path)
3930
if (challengeResponse) {
4031
return challengeResponse
@@ -50,26 +41,10 @@ export async function POST(
5041
const requestId = generateRequestId()
5142
const { path } = await params
5243

53-
// Log ALL incoming webhook requests for debugging
54-
logger.info(`[${requestId}] Incoming webhook request`, {
55-
path,
56-
method: request.method,
57-
headers: Object.fromEntries(request.headers.entries()),
58-
})
59-
60-
// Handle Microsoft Graph subscription validation (some environments send POST with validationToken)
61-
try {
62-
const url = new URL(request.url)
63-
const validationToken = url.searchParams.get('validationToken')
64-
if (validationToken) {
65-
logger.info(`[${requestId}] Microsoft Graph subscription validation (POST) for path: ${path}`)
66-
return new NextResponse(validationToken, {
67-
status: 200,
68-
headers: { 'Content-Type': 'text/plain' },
69-
})
70-
}
71-
} catch {
72-
// ignore URL parsing errors; proceed to normal handling
44+
// Handle provider challenges before body parsing (Microsoft Graph validationToken, etc.)
45+
const earlyChallenge = await handleProviderChallenges({}, request, requestId, path)
46+
if (earlyChallenge) {
47+
return earlyChallenge
7348
}
7449

7550
const parseResult = await parseWebhookBody(request, requestId)
@@ -99,23 +74,6 @@ export async function POST(
9974
const responses: NextResponse[] = []
10075

10176
for (const { webhook: foundWebhook, workflow: foundWorkflow } of webhooksForPath) {
102-
// Log HubSpot webhook details for debugging
103-
if (foundWebhook.provider === 'hubspot') {
104-
const events = Array.isArray(body) ? body : [body]
105-
const firstEvent = events[0]
106-
107-
logger.info(`[${requestId}] HubSpot webhook received`, {
108-
path,
109-
subscriptionType: firstEvent?.subscriptionType,
110-
objectId: firstEvent?.objectId,
111-
portalId: firstEvent?.portalId,
112-
webhookId: foundWebhook.id,
113-
workflowId: foundWorkflow.id,
114-
triggerId: foundWebhook.providerConfig?.triggerId,
115-
eventCount: events.length,
116-
})
117-
}
118-
11977
const authError = await verifyProviderAuth(
12078
foundWebhook,
12179
foundWorkflow,
@@ -162,32 +120,19 @@ export async function POST(
162120
continue
163121
}
164122

165-
if (foundWebhook.provider === 'microsoft-teams') {
166-
return NextResponse.json(
167-
{
168-
type: 'message',
169-
text: 'An unexpected error occurred during preprocessing',
170-
},
171-
{ status: 500 }
172-
)
173-
}
174-
175-
return NextResponse.json(
176-
{ error: 'An unexpected error occurred during preprocessing' },
177-
{ status: 500 }
123+
return formatProviderErrorResponse(
124+
foundWebhook,
125+
'An unexpected error occurred during preprocessing',
126+
500
178127
)
179128
}
180129

181130
if (foundWebhook.blockId) {
182131
const blockExists = await blockExistsInDeployment(foundWorkflow.id, foundWebhook.blockId)
183132
if (!blockExists) {
184-
// For Grain, if block doesn't exist in deployment, treat as verification request
185-
// Grain validates webhook URLs during creation, and the block may not be deployed yet
186-
if (foundWebhook.provider === 'grain') {
187-
logger.info(
188-
`[${requestId}] Grain webhook verification - block not in deployment, returning 200 OK`
189-
)
190-
return NextResponse.json({ status: 'ok', message: 'Webhook endpoint verified' })
133+
const preDeploymentResponse = handlePreDeploymentVerification(foundWebhook, requestId)
134+
if (preDeploymentResponse) {
135+
return preDeploymentResponse
191136
}
192137

193138
logger.info(
@@ -200,20 +145,8 @@ export async function POST(
200145
}
201146
}
202147

203-
if (foundWebhook.provider === 'stripe') {
204-
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
205-
const eventTypes = providerConfig.eventTypes
206-
207-
if (eventTypes && Array.isArray(eventTypes) && eventTypes.length > 0) {
208-
const eventType = body?.type
209-
210-
if (eventType && !eventTypes.includes(eventType)) {
211-
logger.info(
212-
`[${requestId}] Stripe event type '${eventType}' not in allowed list for webhook ${foundWebhook.id}, skipping`
213-
)
214-
continue
215-
}
216-
}
148+
if (shouldSkipWebhookEvent(foundWebhook, body, requestId)) {
149+
continue
217150
}
218151

219152
const response = await queueWebhookExecution(foundWebhook, foundWorkflow, body, request, {

apps/sim/lib/webhooks/processor.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,17 @@ export async function handleProviderChallenges(
153153
}
154154

155155
const url = new URL(request.url)
156+
157+
// Microsoft Graph subscription validation (can come as GET or POST)
158+
const validationToken = url.searchParams.get('validationToken')
159+
if (validationToken) {
160+
logger.info(`[${requestId}] Microsoft Graph subscription validation for path: ${path}`)
161+
return new NextResponse(validationToken, {
162+
status: 200,
163+
headers: { 'Content-Type': 'text/plain' },
164+
})
165+
}
166+
156167
const mode = url.searchParams.get('hub.mode')
157168
const token = url.searchParams.get('hub.verify_token')
158169
const challenge = url.searchParams.get('hub.challenge')
@@ -193,6 +204,61 @@ export function handleProviderReachabilityTest(
193204
return null
194205
}
195206

207+
/**
208+
* Format error response based on provider requirements.
209+
* Some providers (like Microsoft Teams) require specific response formats.
210+
*/
211+
export function formatProviderErrorResponse(
212+
webhook: any,
213+
error: string,
214+
status: number
215+
): NextResponse {
216+
if (webhook.provider === 'microsoft-teams') {
217+
return NextResponse.json({ type: 'message', text: error }, { status })
218+
}
219+
return NextResponse.json({ error }, { status })
220+
}
221+
222+
/**
223+
* Check if a webhook event should be skipped based on provider-specific filtering.
224+
* Returns true if the event should be skipped, false if it should be processed.
225+
*/
226+
export function shouldSkipWebhookEvent(webhook: any, body: any, requestId: string): boolean {
227+
const providerConfig = (webhook.providerConfig as Record<string, any>) || {}
228+
229+
if (webhook.provider === 'stripe') {
230+
const eventTypes = providerConfig.eventTypes
231+
if (eventTypes && Array.isArray(eventTypes) && eventTypes.length > 0) {
232+
const eventType = body?.type
233+
if (eventType && !eventTypes.includes(eventType)) {
234+
logger.info(
235+
`[${requestId}] Stripe event type '${eventType}' not in allowed list for webhook ${webhook.id}, skipping`
236+
)
237+
return true
238+
}
239+
}
240+
}
241+
242+
return false
243+
}
244+
245+
/** Providers that validate webhook URLs during creation, before workflow deployment */
246+
const PROVIDERS_WITH_PRE_DEPLOYMENT_VERIFICATION = new Set(['grain'])
247+
248+
/** Returns 200 OK for providers that validate URLs before the workflow is deployed */
249+
export function handlePreDeploymentVerification(
250+
webhook: any,
251+
requestId: string
252+
): NextResponse | null {
253+
if (PROVIDERS_WITH_PRE_DEPLOYMENT_VERIFICATION.has(webhook.provider)) {
254+
logger.info(
255+
`[${requestId}] ${webhook.provider} webhook - block not in deployment, returning 200 OK for URL validation`
256+
)
257+
return NextResponse.json({ status: 'ok', message: 'Webhook endpoint verified' })
258+
}
259+
return null
260+
}
261+
196262
export async function findWebhookAndWorkflow(
197263
options: WebhookProcessorOptions
198264
): Promise<{ webhook: any; workflow: any } | null> {

0 commit comments

Comments
 (0)