Skip to content

Commit 923595f

Browse files
waleedlatif1waleed
authored andcommitted
fix(webhooks): use next public app url instead of request origin for webhook registration (#1596)
* fix(webhooks): use next public app url instead of request origin for webhook registration * ack PR comments * ci: pin Bun to v1.2.22 to avoid Bun 1.3 breaking changes
1 parent 241d9fd commit 923595f

File tree

7 files changed

+36
-61
lines changed

7 files changed

+36
-61
lines changed

.github/workflows/test-build.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
- name: Setup Bun
1717
uses: oven-sh/setup-bun@v2
1818
with:
19-
bun-version: latest
19+
bun-version: 1.2.22
2020

2121
- name: Setup Node
2222
uses: actions/setup-node@v4

apps/sim/app/api/webhooks/[id]/route.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -282,11 +282,13 @@ export async function DELETE(
282282

283283
if (!resolvedExternalId) {
284284
try {
285-
const requestOrigin = new URL(request.url).origin
286-
const effectiveOrigin = requestOrigin.includes('localhost')
287-
? env.NEXT_PUBLIC_APP_URL || requestOrigin
288-
: requestOrigin
289-
const expectedNotificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${foundWebhook.path}`
285+
if (!env.NEXT_PUBLIC_APP_URL) {
286+
logger.error(
287+
`[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot match Airtable webhook`
288+
)
289+
throw new Error('NEXT_PUBLIC_APP_URL must be configured')
290+
}
291+
const expectedNotificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${foundWebhook.path}`
290292

291293
const listUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks`
292294
const listResp = await fetch(listUrl, {

apps/sim/app/api/webhooks/route.ts

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -432,25 +432,20 @@ async function createAirtableWebhookSubscription(
432432
logger.warn(
433433
`[${requestId}] Could not retrieve Airtable access token for user ${userId}. Cannot create webhook in Airtable.`
434434
)
435-
// Instead of silently returning, throw an error with clear user guidance
436435
throw new Error(
437436
'Airtable account connection required. Please connect your Airtable account in the trigger configuration and try again.'
438437
)
439438
}
440439

441-
const requestOrigin = new URL(request.url).origin
442-
// Ensure origin does not point to localhost for external API calls
443-
const effectiveOrigin = requestOrigin.includes('localhost')
444-
? env.NEXT_PUBLIC_APP_URL || requestOrigin // Use env var if available, fallback to original
445-
: requestOrigin
446-
447-
const notificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${path}`
448-
if (effectiveOrigin !== requestOrigin) {
449-
logger.debug(
450-
`[${requestId}] Remapped localhost origin to ${effectiveOrigin} for notificationUrl`
440+
if (!env.NEXT_PUBLIC_APP_URL) {
441+
logger.error(
442+
`[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot register Airtable webhook`
451443
)
444+
throw new Error('NEXT_PUBLIC_APP_URL must be configured for Airtable webhook registration')
452445
}
453446

447+
const notificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${path}`
448+
454449
const airtableApiUrl = `https://api.airtable.com/v0/bases/${baseId}/webhooks`
455450

456451
const specification: any = {
@@ -549,19 +544,15 @@ async function createTelegramWebhookSubscription(
549544
return // Cannot proceed without botToken
550545
}
551546

552-
const requestOrigin = new URL(request.url).origin
553-
// Ensure origin does not point to localhost for external API calls
554-
const effectiveOrigin = requestOrigin.includes('localhost')
555-
? env.NEXT_PUBLIC_APP_URL || requestOrigin // Use env var if available, fallback to original
556-
: requestOrigin
557-
558-
const notificationUrl = `${effectiveOrigin}/api/webhooks/trigger/${path}`
559-
if (effectiveOrigin !== requestOrigin) {
560-
logger.debug(
561-
`[${requestId}] Remapped localhost origin to ${effectiveOrigin} for notificationUrl`
547+
if (!env.NEXT_PUBLIC_APP_URL) {
548+
logger.error(
549+
`[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot register Telegram webhook`
562550
)
551+
throw new Error('NEXT_PUBLIC_APP_URL must be configured for Telegram webhook registration')
563552
}
564553

554+
const notificationUrl = `${env.NEXT_PUBLIC_APP_URL}/api/webhooks/trigger/${path}`
555+
565556
const telegramApiUrl = `https://api.telegram.org/bot${botToken}/setWebhook`
566557

567558
const requestBody: any = {

apps/sim/app/api/webhooks/test/route.ts

Lines changed: 12 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { db } from '@sim/db'
22
import { webhook } from '@sim/db/schema'
33
import { eq } from 'drizzle-orm'
44
import { type NextRequest, NextResponse } from 'next/server'
5+
import { env } from '@/lib/env'
56
import { createLogger } from '@/lib/logs/console/logger'
67
import { generateRequestId } from '@/lib/utils'
78

@@ -13,7 +14,6 @@ export async function GET(request: NextRequest) {
1314
const requestId = generateRequestId()
1415

1516
try {
16-
// Get the webhook ID and provider from the query parameters
1717
const { searchParams } = new URL(request.url)
1818
const webhookId = searchParams.get('id')
1919

@@ -24,7 +24,6 @@ export async function GET(request: NextRequest) {
2424

2525
logger.debug(`[${requestId}] Testing webhook with ID: ${webhookId}`)
2626

27-
// Find the webhook in the database
2827
const webhooks = await db.select().from(webhook).where(eq(webhook.id, webhookId)).limit(1)
2928

3029
if (webhooks.length === 0) {
@@ -36,8 +35,14 @@ export async function GET(request: NextRequest) {
3635
const provider = foundWebhook.provider || 'generic'
3736
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
3837

39-
// Construct the webhook URL
40-
const baseUrl = new URL(request.url).origin
38+
if (!env.NEXT_PUBLIC_APP_URL) {
39+
logger.error(`[${requestId}] NEXT_PUBLIC_APP_URL not configured, cannot test webhook`)
40+
return NextResponse.json(
41+
{ success: false, error: 'NEXT_PUBLIC_APP_URL must be configured' },
42+
{ status: 500 }
43+
)
44+
}
45+
const baseUrl = env.NEXT_PUBLIC_APP_URL
4146
const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}`
4247

4348
logger.info(`[${requestId}] Testing webhook for provider: ${provider}`, {
@@ -46,7 +51,6 @@ export async function GET(request: NextRequest) {
4651
isActive: foundWebhook.isActive,
4752
})
4853

49-
// Provider-specific test logic
5054
switch (provider) {
5155
case 'whatsapp': {
5256
const verificationToken = providerConfig.verificationToken
@@ -59,30 +63,25 @@ export async function GET(request: NextRequest) {
5963
)
6064
}
6165

62-
// Generate a test challenge
6366
const challenge = `test_${Date.now()}`
6467

65-
// Construct the WhatsApp verification URL
6668
const whatsappUrl = `${webhookUrl}?hub.mode=subscribe&hub.verify_token=${verificationToken}&hub.challenge=${challenge}`
6769

6870
logger.debug(`[${requestId}] Testing WhatsApp webhook verification`, {
6971
webhookId,
7072
challenge,
7173
})
7274

73-
// Make a request to the webhook endpoint
7475
const response = await fetch(whatsappUrl, {
7576
headers: {
7677
'User-Agent': 'facebookplatform/1.0',
7778
},
7879
})
7980

80-
// Get the response details
8181
const status = response.status
8282
const contentType = response.headers.get('content-type')
8383
const responseText = await response.text()
8484

85-
// Check if the test was successful
8685
const success = status === 200 && responseText === challenge
8786

8887
if (success) {
@@ -139,7 +138,6 @@ export async function GET(request: NextRequest) {
139138
)
140139
}
141140

142-
// Test the webhook endpoint with a simple message to check if it's reachable
143141
const testMessage = {
144142
update_id: 12345,
145143
message: {
@@ -165,7 +163,6 @@ export async function GET(request: NextRequest) {
165163
url: webhookUrl,
166164
})
167165

168-
// Make a test request to the webhook endpoint
169166
const response = await fetch(webhookUrl, {
170167
method: 'POST',
171168
headers: {
@@ -175,16 +172,12 @@ export async function GET(request: NextRequest) {
175172
body: JSON.stringify(testMessage),
176173
})
177174

178-
// Get the response details
179175
const status = response.status
180176
let responseText = ''
181177
try {
182178
responseText = await response.text()
183-
} catch (_e) {
184-
// Ignore if we can't get response text
185-
}
179+
} catch (_e) {}
186180

187-
// Consider success if we get a 2xx response
188181
const success = status >= 200 && status < 300
189182

190183
if (success) {
@@ -196,7 +189,6 @@ export async function GET(request: NextRequest) {
196189
})
197190
}
198191

199-
// Get webhook info from Telegram API
200192
let webhookInfo = null
201193
try {
202194
const webhookInfoUrl = `https://api.telegram.org/bot${botToken}/getWebhookInfo`
@@ -215,7 +207,6 @@ export async function GET(request: NextRequest) {
215207
logger.warn(`[${requestId}] Failed to get Telegram webhook info`, e)
216208
}
217209

218-
// Format the curl command for testing
219210
const curlCommand = [
220211
`curl -X POST "${webhookUrl}"`,
221212
`-H "Content-Type: application/json"`,
@@ -288,16 +279,13 @@ export async function GET(request: NextRequest) {
288279
}
289280

290281
case 'generic': {
291-
// Get the general webhook configuration
292282
const token = providerConfig.token
293283
const secretHeaderName = providerConfig.secretHeaderName
294284
const requireAuth = providerConfig.requireAuth
295285
const allowedIps = providerConfig.allowedIps
296286

297-
// Generate sample curl command for testing
298287
let curlCommand = `curl -X POST "${webhookUrl}" -H "Content-Type: application/json"`
299288

300-
// Add auth headers to the curl command if required
301289
if (requireAuth && token) {
302290
if (secretHeaderName) {
303291
curlCommand += ` -H "${secretHeaderName}: ${token}"`
@@ -306,7 +294,6 @@ export async function GET(request: NextRequest) {
306294
}
307295
}
308296

309-
// Add a sample payload
310297
curlCommand += ` -d '{"event":"test_event","timestamp":"${new Date().toISOString()}"}'`
311298

312299
logger.info(`[${requestId}] General webhook test successful: ${webhookId}`)
@@ -391,7 +378,6 @@ export async function GET(request: NextRequest) {
391378
})
392379
}
393380

394-
// Add the Airtable test case
395381
case 'airtable': {
396382
const baseId = providerConfig.baseId
397383
const tableId = providerConfig.tableId
@@ -408,7 +394,6 @@ export async function GET(request: NextRequest) {
408394
)
409395
}
410396

411-
// Define a sample payload structure
412397
const samplePayload = {
413398
webhook: {
414399
id: 'whiYOUR_WEBHOOK_ID',
@@ -418,16 +403,15 @@ export async function GET(request: NextRequest) {
418403
},
419404
payloadFormat: 'v0',
420405
actionMetadata: {
421-
source: 'tableOrViewChange', // Example source
406+
source: 'tableOrViewChange',
422407
sourceMetadata: {},
423408
},
424409
payloads: [
425410
{
426411
timestamp: new Date().toISOString(),
427-
baseTransactionNumber: Date.now(), // Example transaction number
412+
baseTransactionNumber: Date.now(),
428413
changedTablesById: {
429414
[tableId]: {
430-
// Example changes - structure may vary based on actual event
431415
changedRecordsById: {
432416
recSAMPLEID1: {
433417
current: { cellValuesByFieldId: { fldSAMPLEID: 'New Value' } },
@@ -442,7 +426,6 @@ export async function GET(request: NextRequest) {
442426
],
443427
}
444428

445-
// Generate sample curl command
446429
let curlCommand = `curl -X POST "${webhookUrl}" -H "Content-Type: application/json"`
447430
curlCommand += ` -d '${JSON.stringify(samplePayload, null, 2)}'`
448431

@@ -519,7 +502,6 @@ export async function GET(request: NextRequest) {
519502
}
520503

521504
default: {
522-
// Generic webhook test
523505
logger.info(`[${requestId}] Generic webhook test successful: ${webhookId}`)
524506
return NextResponse.json({
525507
success: true,

docker/app.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# ========================================
22
# Base Stage: Alpine Linux with Bun
33
# ========================================
4-
FROM oven/bun:alpine AS base
4+
FROM oven/bun:1.2.22-alpine AS base
55

66
# ========================================
77
# Dependencies Stage: Install Dependencies

docker/db.Dockerfile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# ========================================
22
# Dependencies Stage: Install Dependencies
33
# ========================================
4-
FROM oven/bun:1.2.21-alpine AS deps
4+
FROM oven/bun:1.2.22-alpine AS deps
55
WORKDIR /app
66

77
# Copy only package files needed for migrations
@@ -14,7 +14,7 @@ RUN bun install --ignore-scripts
1414
# ========================================
1515
# Runner Stage: Production Environment
1616
# ========================================
17-
FROM oven/bun:1.2.21-alpine AS runner
17+
FROM oven/bun:1.2.22-alpine AS runner
1818
WORKDIR /app
1919

2020
# Copy only the necessary files from deps

docker/realtime.Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# ========================================
22
# Base Stage: Alpine Linux with Bun
33
# ========================================
4-
FROM oven/bun:alpine AS base
4+
FROM oven/bun:1.2.22-alpine AS base
55

66
# ========================================
77
# Dependencies Stage: Install Dependencies

0 commit comments

Comments
 (0)