@@ -5,9 +5,7 @@ import { type NextRequest, NextResponse } from 'next/server'
55import { getSession } from '@/lib/auth'
66import { createLogger } from '@/lib/logs/console/logger'
77import { getUserEntityPermissions } from '@/lib/permissions/utils'
8- import { getBaseUrl } from '@/lib/urls/utils'
98import { generateRequestId } from '@/lib/utils'
10- import { getOAuthToken } from '@/app/api/auth/oauth/utils'
119
1210const logger = createLogger ( 'WebhookAPI' )
1311
@@ -245,219 +243,9 @@ export async function DELETE(
245243
246244 const foundWebhook = webhookData . webhook
247245
248- // If it's an Airtable webhook, delete it from Airtable first
249- if ( foundWebhook . provider === 'airtable' ) {
250- try {
251- const { baseId, externalId } = ( foundWebhook . providerConfig || { } ) as {
252- baseId ?: string
253- externalId ?: string
254- }
255-
256- if ( ! baseId ) {
257- logger . warn ( `[${ requestId } ] Missing baseId for Airtable webhook deletion.` , {
258- webhookId : id ,
259- } )
260- return NextResponse . json (
261- { error : 'Missing baseId for Airtable webhook deletion' } ,
262- { status : 400 }
263- )
264- }
265-
266- // Get access token for the workflow owner
267- const userIdForToken = webhookData . workflow . userId
268- const accessToken = await getOAuthToken ( userIdForToken , 'airtable' )
269- if ( ! accessToken ) {
270- logger . warn (
271- `[${ requestId } ] Could not retrieve Airtable access token for user ${ userIdForToken } . Cannot delete webhook in Airtable.` ,
272- { webhookId : id }
273- )
274- return NextResponse . json (
275- { error : 'Airtable access token not found for webhook deletion' } ,
276- { status : 401 }
277- )
278- }
279-
280- // Resolve externalId if missing by listing webhooks and matching our notificationUrl
281- let resolvedExternalId : string | undefined = externalId
282-
283- if ( ! resolvedExternalId ) {
284- try {
285- const expectedNotificationUrl = `${ getBaseUrl ( ) } /api/webhooks/trigger/${ foundWebhook . path } `
286-
287- const listUrl = `https://api.airtable.com/v0/bases/${ baseId } /webhooks`
288- const listResp = await fetch ( listUrl , {
289- headers : {
290- Authorization : `Bearer ${ accessToken } ` ,
291- } ,
292- } )
293- const listBody = await listResp . json ( ) . catch ( ( ) => null )
294-
295- if ( listResp . ok && listBody && Array . isArray ( listBody . webhooks ) ) {
296- const match = listBody . webhooks . find ( ( w : any ) => {
297- const url : string | undefined = w ?. notificationUrl
298- if ( ! url ) return false
299- // Prefer exact match; fallback to suffix match to handle origin/host remaps
300- return (
301- url === expectedNotificationUrl ||
302- url . endsWith ( `/api/webhooks/trigger/${ foundWebhook . path } ` )
303- )
304- } )
305- if ( match ?. id ) {
306- resolvedExternalId = match . id as string
307- // Persist resolved externalId for future operations
308- try {
309- await db
310- . update ( webhook )
311- . set ( {
312- providerConfig : {
313- ...( foundWebhook . providerConfig || { } ) ,
314- externalId : resolvedExternalId ,
315- } ,
316- updatedAt : new Date ( ) ,
317- } )
318- . where ( eq ( webhook . id , id ) )
319- } catch {
320- // non-fatal persistence error
321- }
322- logger . info ( `[${ requestId } ] Resolved Airtable externalId by listing webhooks` , {
323- baseId,
324- externalId : resolvedExternalId ,
325- } )
326- } else {
327- logger . warn ( `[${ requestId } ] Could not resolve Airtable externalId from list` , {
328- baseId,
329- expectedNotificationUrl,
330- } )
331- }
332- } else {
333- logger . warn ( `[${ requestId } ] Failed to list Airtable webhooks to resolve externalId` , {
334- baseId,
335- status : listResp . status ,
336- body : listBody ,
337- } )
338- }
339- } catch ( e : any ) {
340- logger . warn ( `[${ requestId } ] Error attempting to resolve Airtable externalId` , {
341- error : e ?. message ,
342- } )
343- }
344- }
345-
346- // If still not resolvable, skip remote deletion but proceed with local delete
347- if ( ! resolvedExternalId ) {
348- logger . info (
349- `[${ requestId } ] Airtable externalId not found; skipping remote deletion and proceeding to remove local record` ,
350- { baseId }
351- )
352- }
353-
354- if ( resolvedExternalId ) {
355- const airtableDeleteUrl = `https://api.airtable.com/v0/bases/${ baseId } /webhooks/${ resolvedExternalId } `
356- const airtableResponse = await fetch ( airtableDeleteUrl , {
357- method : 'DELETE' ,
358- headers : {
359- Authorization : `Bearer ${ accessToken } ` ,
360- } ,
361- } )
362-
363- // Attempt to parse error body for better diagnostics
364- if ( ! airtableResponse . ok ) {
365- let responseBody : any = null
366- try {
367- responseBody = await airtableResponse . json ( )
368- } catch {
369- // ignore parse errors
370- }
371-
372- logger . error (
373- `[${ requestId } ] Failed to delete Airtable webhook in Airtable. Status: ${ airtableResponse . status } ` ,
374- { baseId, externalId : resolvedExternalId , response : responseBody }
375- )
376- return NextResponse . json (
377- {
378- error : 'Failed to delete webhook from Airtable' ,
379- details :
380- ( responseBody && ( responseBody . error ?. message || responseBody . error ) ) ||
381- `Status ${ airtableResponse . status } ` ,
382- } ,
383- { status : 500 }
384- )
385- }
386-
387- logger . info ( `[${ requestId } ] Successfully deleted Airtable webhook in Airtable` , {
388- baseId,
389- externalId : resolvedExternalId ,
390- } )
391- }
392- } catch ( error : any ) {
393- logger . error ( `[${ requestId } ] Error deleting Airtable webhook` , {
394- webhookId : id ,
395- error : error . message ,
396- stack : error . stack ,
397- } )
398- return NextResponse . json (
399- { error : 'Failed to delete webhook from Airtable' , details : error . message } ,
400- { status : 500 }
401- )
402- }
403- }
404-
405- // Delete Microsoft Teams subscription if applicable
406- if ( foundWebhook . provider === 'microsoftteams' ) {
407- const { deleteTeamsSubscription } = await import ( '@/lib/webhooks/webhook-helpers' )
408- logger . info ( `[${ requestId } ] Deleting Teams subscription for webhook ${ id } ` )
409- await deleteTeamsSubscription ( foundWebhook , webhookData . workflow , requestId )
410- // Don't fail webhook deletion if subscription cleanup fails
411- }
412-
413- // Delete Telegram webhook if applicable
414- if ( foundWebhook . provider === 'telegram' ) {
415- try {
416- const { botToken } = ( foundWebhook . providerConfig || { } ) as { botToken ?: string }
417-
418- if ( ! botToken ) {
419- logger . warn ( `[${ requestId } ] Missing botToken for Telegram webhook deletion.` , {
420- webhookId : id ,
421- } )
422- return NextResponse . json (
423- { error : 'Missing botToken for Telegram webhook deletion' } ,
424- { status : 400 }
425- )
426- }
427-
428- const telegramApiUrl = `https://api.telegram.org/bot${ botToken } /deleteWebhook`
429- const telegramResponse = await fetch ( telegramApiUrl , {
430- method : 'POST' ,
431- headers : { 'Content-Type' : 'application/json' } ,
432- } )
433-
434- const responseBody = await telegramResponse . json ( )
435- if ( ! telegramResponse . ok || ! responseBody . ok ) {
436- const errorMessage =
437- responseBody . description ||
438- `Failed to delete Telegram webhook. Status: ${ telegramResponse . status } `
439- logger . error ( `[${ requestId } ] ${ errorMessage } ` , { response : responseBody } )
440- return NextResponse . json (
441- { error : 'Failed to delete webhook from Telegram' , details : errorMessage } ,
442- { status : 500 }
443- )
444- }
445-
446- logger . info ( `[${ requestId } ] Successfully deleted Telegram webhook for webhook ${ id } ` )
447- } catch ( error : any ) {
448- logger . error ( `[${ requestId } ] Error deleting Telegram webhook` , {
449- webhookId : id ,
450- error : error . message ,
451- stack : error . stack ,
452- } )
453- return NextResponse . json (
454- { error : 'Failed to delete webhook from Telegram' , details : error . message } ,
455- { status : 500 }
456- )
457- }
458- }
246+ const { cleanupExternalWebhook } = await import ( '@/lib/webhooks/webhook-helpers' )
247+ await cleanupExternalWebhook ( foundWebhook , webhookData . workflow , requestId )
459248
460- // Delete the webhook from the database
461249 await db . delete ( webhook ) . where ( eq ( webhook . id , id ) )
462250
463251 logger . info ( `[${ requestId } ] Successfully deleted webhook: ${ id } ` )
0 commit comments