Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions apps/sim/app/api/webhooks/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -352,12 +352,15 @@ async function createAirtableWebhookSubscription(
return // Cannot proceed without base/table IDs
}

const accessToken = await getOAuthToken(userId, 'airtable') // Use 'airtable' as the providerId key
const accessToken = await getOAuthToken(userId, 'airtable')
if (!accessToken) {
logger.warn(
`[${requestId}] Could not retrieve Airtable access token for user ${userId}. Cannot create webhook in Airtable.`
)
return
// Instead of silently returning, throw an error with clear user guidance
throw new Error(
'Airtable account connection required. Please connect your Airtable account in the trigger configuration and try again.'
)
}

const requestOrigin = new URL(request.url).origin
Expand Down
29 changes: 25 additions & 4 deletions apps/sim/app/api/webhooks/trigger/[path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,20 +100,41 @@ export async function POST(
return new NextResponse('Failed to read request body', { status: 400 })
}

// Parse the body as JSON
// Parse the body - handle both JSON and form-encoded payloads
let body: any
try {
body = JSON.parse(rawBody)
// Check content type to handle both JSON and form-encoded payloads
const contentType = request.headers.get('content-type') || ''

if (contentType.includes('application/x-www-form-urlencoded')) {
// GitHub sends form-encoded data with JSON in the 'payload' field
const formData = new URLSearchParams(rawBody)
const payloadString = formData.get('payload')

if (!payloadString) {
logger.warn(`[${requestId}] No payload field found in form-encoded data`)
return new NextResponse('Missing payload field', { status: 400 })
}

body = JSON.parse(payloadString)
logger.debug(`[${requestId}] Parsed form-encoded GitHub webhook payload`)
} else {
// Default to JSON parsing
body = JSON.parse(rawBody)
logger.debug(`[${requestId}] Parsed JSON webhook payload`)
}

if (Object.keys(body).length === 0) {
logger.warn(`[${requestId}] Rejecting empty JSON object`)
return new NextResponse('Empty JSON payload', { status: 400 })
}
} catch (parseError) {
logger.error(`[${requestId}] Failed to parse JSON body`, {
logger.error(`[${requestId}] Failed to parse webhook body`, {
error: parseError instanceof Error ? parseError.message : String(parseError),
contentType: request.headers.get('content-type'),
bodyPreview: `${rawBody?.slice(0, 100)}...`,
})
return new NextResponse('Invalid JSON payload', { status: 400 })
return new NextResponse('Invalid payload format', { status: 400 })
}

// Handle Slack challenge
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ export function TriggerModal({
setSelectedCredentialId(credentialValue)
if (triggerDef.provider === 'gmail') {
loadGmailLabels(credentialValue)
} else if (triggerDef.provider === 'outlook') {
loadOutlookFolders(credentialValue)
}
}
}
Expand Down Expand Up @@ -139,6 +141,30 @@ export function TriggerModal({
}
}

// Load Outlook folders for the selected credential
const loadOutlookFolders = async (credentialId: string) => {
try {
const response = await fetch(`/api/tools/outlook/folders?credentialId=${credentialId}`)
if (response.ok) {
const data = await response.json()
if (data.folders && Array.isArray(data.folders)) {
const folderOptions = data.folders.map((folder: any) => ({
id: folder.id,
name: folder.name,
}))
setDynamicOptions((prev) => ({
...prev,
folderIds: folderOptions,
}))
}
} else {
logger.error('Failed to load Outlook folders:', response.statusText)
}
} catch (error) {
logger.error('Error loading Outlook folders:', error)
}
}

// Generate webhook path and URL
useEffect(() => {
// For triggers that don't use webhooks (like Gmail polling), skip URL generation
Expand All @@ -152,15 +178,14 @@ export function TriggerModal({

// If no path exists, generate one automatically
if (!finalPath) {
const timestamp = Date.now()
const randomId = Math.random().toString(36).substring(2, 8)
finalPath = `/${triggerDef.provider}/${timestamp}-${randomId}`
// Use UUID format consistent with other webhooks
finalPath = crypto.randomUUID()
setGeneratedPath(finalPath)
}

if (finalPath) {
const baseUrl = window.location.origin
setWebhookUrl(`${baseUrl}/api/webhooks/trigger${finalPath}`)
setWebhookUrl(`${baseUrl}/api/webhooks/trigger/${finalPath}`)
}
}, [triggerPath, triggerDef.provider, triggerDef.requiresCredentials, triggerDef.webhook])

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export {
AirtableConfig,
DiscordConfig,
GenericConfig,
GithubConfig,
GmailConfig,
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export { AirtableConfig } from './airtable'
export { DiscordConfig } from './discord'
export { GenericConfig } from './generic'
export { GithubConfig } from './github'
export { GmailConfig } from './gmail'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,10 @@ export function SlackConfig({
<li>Paste the Webhook URL (from above) into the "Request URL" field</li>
</ol>
</li>
<li>
Go to <strong>Install App</strong> in the left sidebar and install the app into your
desired Slack workspace and channel.
</li>
<li>Save changes in both Slack and here.</li>
</ol>
</InstructionsSection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { createLogger } from '@/lib/logs/console/logger'
import {
AirtableConfig,
DeleteConfirmDialog,
DiscordConfig,
GenericConfig,
GithubConfig,
GmailConfig,
Expand Down Expand Up @@ -83,8 +82,7 @@ export function WebhookModal({
// Provider-specific state
const [whatsappVerificationToken, setWhatsappVerificationToken] = useState('')
const [githubContentType, setGithubContentType] = useState('application/json')
const [discordWebhookName, setDiscordWebhookName] = useState('')
const [discordAvatarUrl, setDiscordAvatarUrl] = useState('')

const [slackSigningSecret, setSlackSigningSecret] = useState('')
const [telegramBotToken, setTelegramBotToken] = useState('')
// Microsoft Teams-specific state
Expand All @@ -106,8 +104,7 @@ export function WebhookModal({
secretHeaderName: '',
requireAuth: false,
allowedIps: '',
discordWebhookName: '',
discordAvatarUrl: '',

airtableWebhookSecret: '',
airtableBaseId: '',
airtableTableId: '',
Expand Down Expand Up @@ -184,18 +181,6 @@ export function WebhookModal({
const contentType = config.contentType || 'application/json'
setGithubContentType(contentType)
setOriginalValues((prev) => ({ ...prev, githubContentType: contentType }))
} else if (webhookProvider === 'discord') {
const webhookName = config.webhookName || ''
const avatarUrl = config.avatarUrl || ''

setDiscordWebhookName(webhookName)
setDiscordAvatarUrl(avatarUrl)

setOriginalValues((prev) => ({
...prev,
discordWebhookName: webhookName,
discordAvatarUrl: avatarUrl,
}))
} else if (webhookProvider === 'generic') {
// Set general webhook configuration
const token = config.token || ''
Expand Down Expand Up @@ -328,9 +313,6 @@ export function WebhookModal({
(webhookProvider === 'whatsapp' &&
whatsappVerificationToken !== originalValues.whatsappVerificationToken) ||
(webhookProvider === 'github' && githubContentType !== originalValues.githubContentType) ||
(webhookProvider === 'discord' &&
(discordWebhookName !== originalValues.discordWebhookName ||
discordAvatarUrl !== originalValues.discordAvatarUrl)) ||
(webhookProvider === 'generic' &&
(generalToken !== originalValues.generalToken ||
secretHeaderName !== originalValues.secretHeaderName ||
Expand All @@ -357,8 +339,6 @@ export function WebhookModal({
webhookProvider,
whatsappVerificationToken,
githubContentType,
discordWebhookName,
discordAvatarUrl,
generalToken,
secretHeaderName,
requireAuth,
Expand Down Expand Up @@ -393,9 +373,7 @@ export function WebhookModal({
case 'github':
isValid = generalToken.trim() !== ''
break
case 'discord':
isValid = discordWebhookName.trim() !== ''
break

case 'telegram':
isValid = telegramBotToken.trim() !== ''
break
Expand Down Expand Up @@ -442,11 +420,6 @@ export function WebhookModal({
return { verificationToken: whatsappVerificationToken }
case 'github':
return { contentType: githubContentType }
case 'discord':
return {
webhookName: discordWebhookName || undefined,
avatarUrl: discordAvatarUrl || undefined,
}
case 'stripe':
return {}
case 'gmail':
Expand Down Expand Up @@ -539,8 +512,6 @@ export function WebhookModal({
secretHeaderName,
requireAuth,
allowedIps,
discordWebhookName,
discordAvatarUrl,
slackSigningSecret,
airtableWebhookSecret,
airtableBaseId,
Expand Down Expand Up @@ -738,20 +709,7 @@ export function WebhookModal({
setIncludeRawEmail={setIncludeRawEmail}
/>
)
case 'discord':
return (
<DiscordConfig
webhookName={discordWebhookName}
setWebhookName={setDiscordWebhookName}
avatarUrl={discordAvatarUrl}
setAvatarUrl={setDiscordAvatarUrl}
isLoadingToken={isLoadingToken}
testResult={testResult}
copied={copied}
copyToClipboard={copyToClipboard}
testWebhook={testWebhook}
/>
)

case 'stripe':
return (
<StripeConfig
Expand Down
Loading