@@ -153,8 +153,66 @@ export async function POST(
153153 if ( foundWebhook . provider === 'airtable' ) {
154154 logger . info ( `[${ requestId } ] Airtable webhook ping received for webhook: ${ foundWebhook . id } ` )
155155
156- // Acknowledge the ping immediately - Now we process synchronously
157- // logger.debug(`[${requestId}] Acknowledging Airtable ping for webhook ${foundWebhook.id}.`)
156+ // Simple deduplication for Airtable webhooks in serverless environments using hash of notificationId
157+ const notificationId = body . notificationId || null
158+
159+ if ( notificationId ) {
160+ // Check if we've already processed this notificationId
161+ try {
162+ const processedKey = `airtable-webhook-${ foundWebhook . id } -${ notificationId } `
163+
164+ // Use the webhook table to store the processed IDs
165+ const alreadyProcessed = await db
166+ . select ( { id : webhook . id } )
167+ . from ( webhook )
168+ . where (
169+ and (
170+ eq ( webhook . id , foundWebhook . id ) ,
171+ sql `(webhook.provider_config->>'processedNotifications')::jsonb ? ${ processedKey } `
172+ )
173+ )
174+ . limit ( 1 )
175+
176+ if ( alreadyProcessed . length > 0 ) {
177+ logger . info (
178+ `[${ requestId } ] Duplicate Airtable notification detected: ${ notificationId } ` ,
179+ {
180+ webhookId : foundWebhook . id ,
181+ }
182+ )
183+ return new NextResponse ( 'Notification already processed' , { status : 200 } )
184+ }
185+
186+ // Add to processed notifications
187+ // Get current provider config
188+ const providerConfig = foundWebhook . providerConfig || { }
189+ // Get current processed notifications or create an empty array
190+ const processedNotifications = providerConfig . processedNotifications || [ ]
191+ // Add the current notification
192+ processedNotifications . push ( processedKey )
193+ // Keep only the last 100 notifications to prevent unlimited growth
194+ const limitedNotifications = processedNotifications . slice ( - 100 )
195+
196+ // Update the webhook with the new processed notifications
197+ await db
198+ . update ( webhook )
199+ . set ( {
200+ providerConfig : {
201+ ...providerConfig ,
202+ processedNotifications : limitedNotifications ,
203+ } ,
204+ updatedAt : new Date ( ) ,
205+ } )
206+ . where ( eq ( webhook . id , foundWebhook . id ) )
207+ } catch ( error ) {
208+ // If deduplication fails, log and continue processing
209+ // It's better to risk duplicate processing than to drop events
210+ logger . warn ( `[${ requestId } ] Deduplication check failed, continuing with processing` , {
211+ error : error instanceof Error ? error . message : String ( error ) ,
212+ webhookId : foundWebhook . id ,
213+ } )
214+ }
215+ }
158216
159217 // Process the ping SYNCHRONOUSLY
160218 try {
@@ -189,12 +247,6 @@ export async function POST(
189247 status : 500 ,
190248 } )
191249 }
192-
193- // REMOVED Asynchronous handling logic:
194- // const backgroundTaskPromise = fetchAndProcessAirtablePayloads(...)
195- // if (typeof (request as any).waitUntil === 'function') { ... }
196- // else { ... }
197- // return new NextResponse('Airtable ping acknowledged, processing started', { status: 200 })
198250 }
199251
200252 // --- Existing Deduplication and Processing for other providers ---
0 commit comments