Skip to content

Commit 2f25305

Browse files
committed
fix(airtable): add back simple dedupe for airtable webhooks
1 parent 8d38627 commit 2f25305

File tree

1 file changed

+60
-8
lines changed
  • sim/app/api/webhooks/trigger/[path]

1 file changed

+60
-8
lines changed

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

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)