mirror of
https://github.com/simstudioai/sim.git
synced 2026-01-22 21:38:05 -05:00
improvement(webhooks): consolidated webhook routes
This commit is contained in:
@@ -1,154 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { getSession } from '@/lib/auth'
|
||||
import { db } from '@/db'
|
||||
import { webhook, workflow } from '@/db/schema'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
try {
|
||||
const { id } = await params
|
||||
|
||||
const session = await getSession()
|
||||
if (!session?.user?.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
// Find the webhook and check ownership
|
||||
const webhooks = await db
|
||||
.select({
|
||||
webhook: webhook,
|
||||
workflow: {
|
||||
id: workflow.id,
|
||||
name: workflow.name,
|
||||
userId: workflow.userId,
|
||||
},
|
||||
})
|
||||
.from(webhook)
|
||||
.innerJoin(workflow, eq(webhook.workflowId, workflow.id))
|
||||
.where(eq(webhook.id, id))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return NextResponse.json({ error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
if (webhooks[0].workflow.userId !== session.user.id) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0].webhook
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
|
||||
// Create a test payload based on the webhook provider
|
||||
let testPayload = {}
|
||||
|
||||
switch (foundWebhook.provider) {
|
||||
case 'whatsapp':
|
||||
testPayload = {
|
||||
entry: [
|
||||
{
|
||||
changes: [
|
||||
{
|
||||
value: {
|
||||
metadata: {
|
||||
phone_number_id: '123456789',
|
||||
},
|
||||
messages: [
|
||||
{
|
||||
from: '9876543210',
|
||||
id: 'test-message-id',
|
||||
timestamp: new Date().toISOString(),
|
||||
text: {
|
||||
body: 'This is a test message from the webhook test endpoint',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
break
|
||||
case 'github':
|
||||
testPayload = {
|
||||
action: 'test',
|
||||
repository: {
|
||||
full_name: 'user/repo',
|
||||
},
|
||||
sender: {
|
||||
login: 'testuser',
|
||||
},
|
||||
}
|
||||
break
|
||||
case 'stripe':
|
||||
testPayload = {
|
||||
id: 'evt_test',
|
||||
type: 'test.webhook',
|
||||
created: Math.floor(Date.now() / 1000),
|
||||
data: {
|
||||
object: {
|
||||
id: 'test_obj_123',
|
||||
},
|
||||
},
|
||||
}
|
||||
break
|
||||
default:
|
||||
testPayload = {
|
||||
event: 'test',
|
||||
timestamp: new Date().toISOString(),
|
||||
data: {
|
||||
message: 'This is a test webhook event',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Make a request to the webhook trigger endpoint
|
||||
const baseUrl = new URL(request.url).origin
|
||||
const webhookPath = foundWebhook.path.startsWith('/')
|
||||
? foundWebhook.path
|
||||
: `/${foundWebhook.path}`
|
||||
const triggerUrl = `${baseUrl}/api/webhooks/trigger${webhookPath}`
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
// Add provider-specific headers
|
||||
if (foundWebhook.provider === 'whatsapp' && providerConfig.verificationToken) {
|
||||
// For WhatsApp, we don't need to add any headers for the test
|
||||
} else if (foundWebhook.provider === 'github' && providerConfig.contentType) {
|
||||
headers['Content-Type'] = providerConfig.contentType
|
||||
} else if (providerConfig.token) {
|
||||
// For generic webhooks with a token
|
||||
headers['Authorization'] = `Bearer ${providerConfig.token}`
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(triggerUrl, {
|
||||
method: 'POST',
|
||||
headers,
|
||||
body: JSON.stringify(testPayload),
|
||||
})
|
||||
|
||||
const responseData = await response.json().catch(() => ({}))
|
||||
|
||||
return NextResponse.json({
|
||||
success: response.ok,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
data: responseData,
|
||||
})
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({
|
||||
success: false,
|
||||
error: error.message || 'Failed to trigger webhook',
|
||||
})
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error testing webhook:', error)
|
||||
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
|
||||
}
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '@/db'
|
||||
import { webhook } from '@/db/schema'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Get the webhook ID from the query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const webhookId = searchParams.get('id')
|
||||
|
||||
if (!webhookId) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db.select().from(webhook).where(eq(webhook.id, webhookId)).limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0]
|
||||
|
||||
// Construct the webhook URL
|
||||
const baseUrl = new URL(request.url).origin
|
||||
const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}`
|
||||
|
||||
// Return the webhook information
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
provider: foundWebhook.provider,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
message: 'Webhook configuration is valid. You can use this URL to receive webhook events.',
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error testing webhook:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Test failed',
|
||||
message: error.message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '@/db'
|
||||
import { webhook } from '@/db/schema'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Get the webhook ID from the query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const webhookId = searchParams.get('id')
|
||||
|
||||
if (!webhookId) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db
|
||||
.select()
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.id, webhookId), eq(webhook.provider, 'github')))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'GitHub webhook not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0]
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const contentType = providerConfig.contentType || 'application/json'
|
||||
|
||||
// Construct the webhook URL
|
||||
const baseUrl = new URL(request.url).origin
|
||||
const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}`
|
||||
|
||||
// Return the webhook information
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
contentType,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
message:
|
||||
'GitHub webhook configuration is valid. Use this URL in your GitHub repository settings.',
|
||||
setup: {
|
||||
url: webhookUrl,
|
||||
contentType,
|
||||
events: ['push', 'pull_request', 'issues', 'issue_comment'],
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error testing GitHub webhook:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Test failed',
|
||||
message: error.message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
178
app/api/webhooks/test/route.ts
Normal file
178
app/api/webhooks/test/route.ts
Normal file
@@ -0,0 +1,178 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '@/db'
|
||||
import { webhook } from '@/db/schema'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Get the webhook ID and provider from the query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const webhookId = searchParams.get('id')
|
||||
|
||||
if (!webhookId) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db.select().from(webhook).where(eq(webhook.id, webhookId)).limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0]
|
||||
const provider = foundWebhook.provider || 'generic'
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
|
||||
// Construct the webhook URL
|
||||
const baseUrl = new URL(request.url).origin
|
||||
const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}`
|
||||
|
||||
// Provider-specific test logic
|
||||
switch (provider) {
|
||||
case 'whatsapp': {
|
||||
const verificationToken = providerConfig.verificationToken
|
||||
|
||||
if (!verificationToken) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Webhook has no verification token' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Generate a test challenge
|
||||
const challenge = `test_${Date.now()}`
|
||||
|
||||
// Construct the WhatsApp verification URL
|
||||
const whatsappUrl = `${webhookUrl}?hub.mode=subscribe&hub.verify_token=${verificationToken}&hub.challenge=${challenge}`
|
||||
|
||||
console.log('Testing WhatsApp webhook:', {
|
||||
webhookId,
|
||||
url: whatsappUrl,
|
||||
token: verificationToken ? verificationToken.substring(0, 3) + '***' : null,
|
||||
})
|
||||
|
||||
// Make a request to the webhook endpoint
|
||||
const response = await fetch(whatsappUrl, {
|
||||
headers: {
|
||||
'User-Agent': 'facebookplatform/1.0',
|
||||
},
|
||||
})
|
||||
|
||||
// Get the response details
|
||||
const status = response.status
|
||||
const contentType = response.headers.get('content-type')
|
||||
const responseText = await response.text()
|
||||
|
||||
console.log('WhatsApp test response:', {
|
||||
status,
|
||||
contentType,
|
||||
responseText,
|
||||
})
|
||||
|
||||
// Check if the test was successful
|
||||
const success = status === 200 && responseText === challenge
|
||||
|
||||
return NextResponse.json({
|
||||
success,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
verificationToken,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
test: {
|
||||
status,
|
||||
contentType,
|
||||
responseText,
|
||||
expectedStatus: 200,
|
||||
expectedContentType: 'text/plain',
|
||||
expectedResponse: challenge,
|
||||
},
|
||||
message: success
|
||||
? 'Webhook configuration is valid. You can now use this URL in WhatsApp.'
|
||||
: 'Webhook verification failed. Please check your configuration.',
|
||||
diagnostics: {
|
||||
statusMatch: status === 200 ? '✅ Status code is 200' : '❌ Status code should be 200',
|
||||
contentTypeMatch:
|
||||
contentType === 'text/plain'
|
||||
? '✅ Content-Type is text/plain'
|
||||
: '❌ Content-Type should be text/plain',
|
||||
bodyMatch:
|
||||
responseText === challenge
|
||||
? '✅ Response body matches challenge'
|
||||
: '❌ Response body should exactly match the challenge string',
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
case 'github': {
|
||||
const contentType = providerConfig.contentType || 'application/json'
|
||||
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
contentType,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
message:
|
||||
'GitHub webhook configuration is valid. Use this URL in your GitHub repository settings.',
|
||||
setup: {
|
||||
url: webhookUrl,
|
||||
contentType,
|
||||
events: ['push', 'pull_request', 'issues', 'issue_comment'],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
case 'stripe': {
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
message: 'Stripe webhook configuration is valid. Use this URL in your Stripe dashboard.',
|
||||
setup: {
|
||||
url: webhookUrl,
|
||||
events: [
|
||||
'charge.succeeded',
|
||||
'invoice.payment_succeeded',
|
||||
'customer.subscription.created',
|
||||
],
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
default: {
|
||||
// Generic webhook test
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
provider: foundWebhook.provider,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
message:
|
||||
'Webhook configuration is valid. You can use this URL to receive webhook events.',
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('Error testing webhook:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Test failed',
|
||||
message: error.message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '@/db'
|
||||
import { webhook } from '@/db/schema'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Get the webhook ID from the query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const webhookId = searchParams.get('id')
|
||||
|
||||
if (!webhookId) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db
|
||||
.select()
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.id, webhookId), eq(webhook.provider, 'stripe')))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Stripe webhook not found' },
|
||||
{ status: 404 }
|
||||
)
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0]
|
||||
|
||||
// Construct the webhook URL
|
||||
const baseUrl = new URL(request.url).origin
|
||||
const webhookUrl = `${baseUrl}/api/webhooks/trigger/${foundWebhook.path}`
|
||||
|
||||
// Return the webhook information
|
||||
return NextResponse.json({
|
||||
success: true,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: webhookUrl,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
message: 'Stripe webhook configuration is valid. Use this URL in your Stripe dashboard.',
|
||||
setup: {
|
||||
url: webhookUrl,
|
||||
events: ['charge.succeeded', 'invoice.payment_succeeded', 'customer.subscription.created'],
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error testing Stripe webhook:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Test failed',
|
||||
message: error.message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,117 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { db } from '@/db'
|
||||
import { webhook } from '@/db/schema'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Get the webhook ID from the query parameters
|
||||
const { searchParams } = new URL(request.url)
|
||||
const webhookId = searchParams.get('id')
|
||||
|
||||
if (!webhookId) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook ID is required' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db
|
||||
.select()
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.id, webhookId), eq(webhook.provider, 'whatsapp')))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return NextResponse.json({ success: false, error: 'Webhook not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
const foundWebhook = webhooks[0]
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
const verificationToken = providerConfig.verificationToken
|
||||
|
||||
if (!verificationToken) {
|
||||
return NextResponse.json(
|
||||
{ success: false, error: 'Webhook has no verification token' },
|
||||
{ status: 400 }
|
||||
)
|
||||
}
|
||||
|
||||
// Generate a test challenge
|
||||
const challenge = `test_${Date.now()}`
|
||||
|
||||
// Construct the WhatsApp verification URL
|
||||
const baseUrl = new URL(request.url).origin
|
||||
const whatsappUrl = `${baseUrl}/api/webhooks/whatsapp?hub.mode=subscribe&hub.verify_token=${verificationToken}&hub.challenge=${challenge}`
|
||||
|
||||
console.log('Testing WhatsApp webhook:', {
|
||||
webhookId,
|
||||
url: whatsappUrl,
|
||||
token: verificationToken ? verificationToken.substring(0, 3) + '***' : null,
|
||||
})
|
||||
|
||||
// Make a request to the WhatsApp webhook endpoint
|
||||
const response = await fetch(whatsappUrl, {
|
||||
headers: {
|
||||
'User-Agent': 'facebookplatform/1.0',
|
||||
},
|
||||
})
|
||||
|
||||
// Get the response details
|
||||
const status = response.status
|
||||
const contentType = response.headers.get('content-type')
|
||||
const responseText = await response.text()
|
||||
|
||||
console.log('WhatsApp test response:', {
|
||||
status,
|
||||
contentType,
|
||||
responseText,
|
||||
})
|
||||
|
||||
// Check if the test was successful
|
||||
const success = status === 200 && responseText === challenge
|
||||
|
||||
// Return the test results
|
||||
return NextResponse.json({
|
||||
success,
|
||||
webhook: {
|
||||
id: foundWebhook.id,
|
||||
url: `${baseUrl}/api/webhooks/whatsapp`,
|
||||
verificationToken,
|
||||
isActive: foundWebhook.isActive,
|
||||
},
|
||||
test: {
|
||||
status,
|
||||
contentType,
|
||||
responseText,
|
||||
expectedStatus: 200,
|
||||
expectedContentType: 'text/plain',
|
||||
expectedResponse: challenge,
|
||||
},
|
||||
message: success
|
||||
? 'Webhook configuration is valid. You can now use this URL in WhatsApp.'
|
||||
: 'Webhook verification failed. Please check your configuration.',
|
||||
diagnostics: {
|
||||
statusMatch: status === 200 ? '✅ Status code is 200' : '❌ Status code should be 200',
|
||||
contentTypeMatch:
|
||||
contentType === 'text/plain'
|
||||
? '✅ Content-Type is text/plain'
|
||||
: '❌ Content-Type should be text/plain',
|
||||
bodyMatch:
|
||||
responseText === challenge
|
||||
? '✅ Response body matches challenge'
|
||||
: '❌ Response body should exactly match the challenge string',
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error('Error testing WhatsApp webhook:', error)
|
||||
return NextResponse.json(
|
||||
{
|
||||
success: false,
|
||||
error: 'Test failed',
|
||||
message: error.message,
|
||||
},
|
||||
{ status: 500 }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,18 +1,99 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { nanoid } from 'nanoid'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { persistLog } from '@/lib/logging'
|
||||
import { decryptSecret } from '@/lib/utils'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { db } from '@/db'
|
||||
import { webhook, workflow } from '@/db/schema'
|
||||
import { environment, webhook, workflow } from '@/db/schema'
|
||||
import { Executor } from '@/executor'
|
||||
import { Serializer } from '@/serializer'
|
||||
import { SerializedWorkflow } from '@/serializer/types'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
/**
|
||||
* Generic webhook trigger endpoint for non-WhatsApp providers
|
||||
* Handles webhooks with a path parameter
|
||||
* Consolidated webhook trigger endpoint for all providers
|
||||
* Handles both WhatsApp verification and other webhook providers
|
||||
*/
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string }> }) {
|
||||
try {
|
||||
const path = (await params).path
|
||||
const url = new URL(request.url)
|
||||
|
||||
// Check if this is a WhatsApp verification request
|
||||
const mode = url.searchParams.get('hub.mode')
|
||||
const token = url.searchParams.get('hub.verify_token')
|
||||
const challenge = url.searchParams.get('hub.challenge')
|
||||
|
||||
if (mode && token && challenge) {
|
||||
// This is a WhatsApp verification request
|
||||
console.log('WhatsApp verification request received')
|
||||
|
||||
if (mode !== 'subscribe') {
|
||||
console.log('Invalid mode:', mode)
|
||||
return new NextResponse('Invalid mode', { status: 400 })
|
||||
}
|
||||
|
||||
// Find all active WhatsApp webhooks
|
||||
const webhooks = await db
|
||||
.select()
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.provider, 'whatsapp'), eq(webhook.isActive, true)))
|
||||
|
||||
// Check if any webhook has a matching verification token
|
||||
for (const wh of webhooks) {
|
||||
const providerConfig = (wh.providerConfig as Record<string, any>) || {}
|
||||
const verificationToken = providerConfig.verificationToken
|
||||
|
||||
if (!verificationToken) {
|
||||
console.log(`Webhook ${wh.id} has no verification token, skipping`)
|
||||
continue
|
||||
}
|
||||
|
||||
if (token === verificationToken) {
|
||||
console.log(
|
||||
`Verification successful for webhook ${wh.id}, returning challenge: ${challenge}`
|
||||
)
|
||||
// Return ONLY the challenge as plain text (exactly as WhatsApp expects)
|
||||
return new NextResponse(challenge, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log('No matching verification token found')
|
||||
return new NextResponse('Verification failed', { status: 403 })
|
||||
}
|
||||
|
||||
// For non-WhatsApp verification requests
|
||||
console.log('Looking for webhook with path:', path)
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db
|
||||
.select({
|
||||
webhook: webhook,
|
||||
})
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.path, path), eq(webhook.isActive, true)))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return new NextResponse('Webhook not found', { status: 404 })
|
||||
}
|
||||
|
||||
// For other providers, just return a 200 OK
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
} catch (error: any) {
|
||||
console.error('Error processing webhook verification:', error)
|
||||
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(
|
||||
request: NextRequest,
|
||||
{ params }: { params: Promise<{ path: string }> }
|
||||
@@ -20,7 +101,9 @@ export async function POST(
|
||||
try {
|
||||
const path = (await params).path
|
||||
|
||||
console.log('Looking for webhook with path:', path)
|
||||
// Parse the request body
|
||||
const body = await request.json().catch(() => ({}))
|
||||
console.log(`Webhook POST request received for path: ${path}`)
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db
|
||||
@@ -38,16 +121,10 @@ export async function POST(
|
||||
}
|
||||
|
||||
const { webhook: foundWebhook, workflow: foundWorkflow } = webhooks[0]
|
||||
|
||||
// Skip WhatsApp webhooks - they should use the dedicated endpoint
|
||||
if (foundWebhook.provider === 'whatsapp') {
|
||||
return new NextResponse('WhatsApp webhooks should use the dedicated endpoint', {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
const executionId = uuidv4()
|
||||
|
||||
// Handle provider-specific verification and authentication
|
||||
if (foundWebhook.provider) {
|
||||
if (foundWebhook.provider && foundWebhook.provider !== 'whatsapp') {
|
||||
const authHeader = request.headers.get('authorization')
|
||||
const providerConfig = (foundWebhook.providerConfig as Record<string, any>) || {}
|
||||
|
||||
@@ -71,74 +148,135 @@ export async function POST(
|
||||
}
|
||||
}
|
||||
|
||||
// Parse the request body
|
||||
const body = await request.json().catch(() => ({}))
|
||||
// Format the input based on provider
|
||||
let input = {}
|
||||
|
||||
// Create execution context with the webhook payload
|
||||
const executionId = nanoid()
|
||||
if (foundWebhook.provider === 'whatsapp') {
|
||||
// Extract WhatsApp specific data
|
||||
const data = body?.entry?.[0]?.changes?.[0]?.value
|
||||
const messages = data?.messages || []
|
||||
|
||||
// Format the input to match the expected BlockOutput type
|
||||
const input = {
|
||||
webhook: {
|
||||
data: {
|
||||
path,
|
||||
provider: foundWebhook.provider,
|
||||
providerConfig: foundWebhook.providerConfig,
|
||||
payload: body,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
method: request.method,
|
||||
if (messages.length > 0) {
|
||||
const message = messages[0]
|
||||
const phoneNumberId = data.metadata?.phone_number_id
|
||||
const from = message.from
|
||||
const messageId = message.id
|
||||
const timestamp = message.timestamp
|
||||
const text = message.text?.body
|
||||
|
||||
console.log(
|
||||
`Received WhatsApp message: ${text ? text.substring(0, 50) : '[no text]'} from ${from}`
|
||||
)
|
||||
|
||||
input = {
|
||||
whatsapp: {
|
||||
data: {
|
||||
messageId,
|
||||
from,
|
||||
phoneNumberId,
|
||||
text,
|
||||
timestamp,
|
||||
raw: message,
|
||||
},
|
||||
},
|
||||
webhook: {
|
||||
data: {
|
||||
provider: 'whatsapp',
|
||||
path: foundWebhook.path,
|
||||
providerConfig: foundWebhook.providerConfig,
|
||||
payload: body,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
method: request.method,
|
||||
},
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// This might be a different type of notification (e.g., status update)
|
||||
console.log('No messages in WhatsApp payload, might be a status update')
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
}
|
||||
} else {
|
||||
// Generic format for other providers
|
||||
input = {
|
||||
webhook: {
|
||||
data: {
|
||||
path,
|
||||
provider: foundWebhook.provider,
|
||||
providerConfig: foundWebhook.providerConfig,
|
||||
payload: body,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
method: request.method,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the workflow
|
||||
if (foundWorkflow.state) {
|
||||
const executor = new Executor(foundWorkflow.state as SerializedWorkflow, input)
|
||||
const result = await executor.execute(foundWorkflow.id)
|
||||
|
||||
// Return the execution result
|
||||
return NextResponse.json(result, { status: 200 })
|
||||
// Get the workflow state
|
||||
if (!foundWorkflow.state) {
|
||||
console.log(`Workflow ${foundWorkflow.id} has no state, skipping`)
|
||||
return new NextResponse('Workflow state not found', { status: 500 })
|
||||
}
|
||||
|
||||
return new NextResponse('Workflow state not found', { status: 500 })
|
||||
console.log(`Executing workflow ${foundWorkflow.id} for webhook ${foundWebhook.id}`)
|
||||
|
||||
// Get the workflow state
|
||||
const state = foundWorkflow.state as any
|
||||
const { blocks, edges, loops } = state
|
||||
|
||||
// Use the same execution flow as in manual executions
|
||||
const mergedStates = mergeSubblockState(blocks)
|
||||
|
||||
// Retrieve environment variables for this user
|
||||
const [userEnv] = await db
|
||||
.select()
|
||||
.from(environment)
|
||||
.where(eq(environment.userId, foundWorkflow.userId))
|
||||
.limit(1)
|
||||
|
||||
// Create a map of decrypted environment variables
|
||||
const decryptedEnvVars: Record<string, string> = {}
|
||||
if (userEnv) {
|
||||
for (const [key, encryptedValue] of Object.entries(
|
||||
userEnv.variables as Record<string, string>
|
||||
)) {
|
||||
try {
|
||||
const { decrypted } = await decryptSecret(encryptedValue)
|
||||
decryptedEnvVars[key] = decrypted
|
||||
} catch (error: any) {
|
||||
console.error(`Failed to decrypt ${key}:`, error)
|
||||
throw new Error(`Failed to decrypt environment variable "${key}": ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize and execute the workflow
|
||||
const serializedWorkflow = new Serializer().serializeWorkflow(mergedStates as any, edges, loops)
|
||||
|
||||
const executor = new Executor(serializedWorkflow, mergedStates as any, decryptedEnvVars, input)
|
||||
const result = await executor.execute(foundWorkflow.id)
|
||||
|
||||
console.log(`Successfully executed workflow ${foundWorkflow.id}`)
|
||||
|
||||
// Log each execution step
|
||||
for (const log of result.logs || []) {
|
||||
await persistLog({
|
||||
id: uuidv4(),
|
||||
workflowId: foundWorkflow.id,
|
||||
executionId,
|
||||
level: log.success ? 'info' : 'error',
|
||||
message: log.success
|
||||
? `Block ${log.blockName || log.blockId} (${log.blockType}): ${JSON.stringify(log.output?.response || {})}`
|
||||
: `Block ${log.blockName || log.blockId} (${log.blockType}): ${log.error || 'Failed'}`,
|
||||
duration: log.success ? `${log.durationMs}ms` : 'NA',
|
||||
trigger: 'webhook',
|
||||
createdAt: new Date(log.endedAt || log.startedAt),
|
||||
})
|
||||
}
|
||||
|
||||
// Return the execution result
|
||||
return NextResponse.json(result, { status: 200 })
|
||||
} catch (error: any) {
|
||||
console.error('Error processing webhook:', error)
|
||||
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest, { params }: { params: Promise<{ path: string }> }) {
|
||||
try {
|
||||
const path = (await params).path
|
||||
|
||||
console.log('Looking for webhook with path:', path)
|
||||
|
||||
// Find the webhook in the database
|
||||
const webhooks = await db
|
||||
.select({
|
||||
webhook: webhook,
|
||||
})
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.path, path), eq(webhook.isActive, true)))
|
||||
.limit(1)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
return new NextResponse('Webhook not found', { status: 404 })
|
||||
}
|
||||
|
||||
const { webhook: foundWebhook } = webhooks[0]
|
||||
|
||||
// Skip WhatsApp webhooks - they should use the dedicated endpoint
|
||||
if (foundWebhook.provider === 'whatsapp') {
|
||||
return new NextResponse('WhatsApp webhooks should use the dedicated endpoint', {
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
|
||||
// For other providers, just return a 200 OK
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
} catch (error: any) {
|
||||
console.error('Error processing webhook verification:', error)
|
||||
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { and, eq } from 'drizzle-orm'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { persistLog } from '@/lib/logging'
|
||||
import { decryptSecret } from '@/lib/utils'
|
||||
import { mergeSubblockState } from '@/stores/workflows/utils'
|
||||
import { db } from '@/db'
|
||||
import { environment, webhook, workflow } from '@/db/schema'
|
||||
import { Executor } from '@/executor'
|
||||
import { Serializer } from '@/serializer'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
/**
|
||||
* WhatsApp webhook endpoint
|
||||
* This follows the exact pattern in the WhatsApp sample app
|
||||
* https://developers.facebook.com/docs/whatsapp/sample-app-endpoints
|
||||
*/
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
// Log the full request details for debugging
|
||||
const url = new URL(request.url)
|
||||
const searchParams = Object.fromEntries(url.searchParams.entries())
|
||||
console.log('WhatsApp webhook GET request received:', {
|
||||
url: url.toString(),
|
||||
params: searchParams,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
})
|
||||
|
||||
// Get the verification parameters
|
||||
const mode = url.searchParams.get('hub.mode')
|
||||
const token = url.searchParams.get('hub.verify_token')
|
||||
const challenge = url.searchParams.get('hub.challenge')
|
||||
|
||||
console.log('WhatsApp verification parameters:', {
|
||||
mode,
|
||||
tokenProvided: Boolean(token),
|
||||
challengeProvided: Boolean(challenge),
|
||||
})
|
||||
|
||||
// Validate the verification parameters
|
||||
if (!mode || !token || !challenge) {
|
||||
console.log('Missing verification parameters')
|
||||
return new NextResponse('Missing verification parameters', { status: 400 })
|
||||
}
|
||||
|
||||
if (mode !== 'subscribe') {
|
||||
console.log('Invalid mode:', mode)
|
||||
return new NextResponse('Invalid mode', { status: 400 })
|
||||
}
|
||||
|
||||
// Find all active WhatsApp webhooks
|
||||
const webhooks = await db
|
||||
.select()
|
||||
.from(webhook)
|
||||
.where(and(eq(webhook.provider, 'whatsapp'), eq(webhook.isActive, true)))
|
||||
|
||||
console.log(`Found ${webhooks.length} active WhatsApp webhooks`)
|
||||
|
||||
// Check if any webhook has a matching verification token
|
||||
for (const wh of webhooks) {
|
||||
const providerConfig = (wh.providerConfig as Record<string, any>) || {}
|
||||
const verificationToken = providerConfig.verificationToken
|
||||
|
||||
if (!verificationToken) {
|
||||
console.log(`Webhook ${wh.id} has no verification token, skipping`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(`Checking webhook ${wh.id} with token ${verificationToken.substring(0, 2)}***`)
|
||||
|
||||
if (token === verificationToken) {
|
||||
console.log(
|
||||
`Verification successful for webhook ${wh.id}, returning challenge: ${challenge}`
|
||||
)
|
||||
// Return ONLY the challenge as plain text (exactly as WhatsApp expects)
|
||||
return new NextResponse(challenge, {
|
||||
status: 200,
|
||||
headers: {
|
||||
'Content-Type': 'text/plain',
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
console.log('No matching verification token found')
|
||||
return new NextResponse('Verification failed', { status: 403 })
|
||||
} catch (error: any) {
|
||||
console.error('Error processing WhatsApp webhook verification:', error)
|
||||
return new NextResponse(`Internal Server Error: ${error.message}`, { status: 500 })
|
||||
}
|
||||
}
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
console.log('WhatsApp webhook POST request received')
|
||||
|
||||
// Parse the request body
|
||||
const body = await request.json().catch(() => ({}))
|
||||
console.log('WhatsApp webhook payload:', JSON.stringify(body).substring(0, 200) + '...')
|
||||
|
||||
// Find all active WhatsApp webhooks
|
||||
const webhooks = await db
|
||||
.select({
|
||||
webhook: webhook,
|
||||
workflow: workflow,
|
||||
})
|
||||
.from(webhook)
|
||||
.innerJoin(workflow, eq(webhook.workflowId, workflow.id))
|
||||
.where(
|
||||
and(
|
||||
eq(webhook.provider, 'whatsapp'),
|
||||
eq(webhook.isActive, true)
|
||||
// No workflow.isDeployed check as we discussed
|
||||
)
|
||||
)
|
||||
|
||||
if (webhooks.length === 0) {
|
||||
console.log('No active WhatsApp webhooks found')
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
}
|
||||
|
||||
console.log(`Found ${webhooks.length} active WhatsApp webhooks`)
|
||||
|
||||
// Extract the WhatsApp message data
|
||||
const data = body?.entry?.[0]?.changes?.[0]?.value
|
||||
|
||||
if (!data) {
|
||||
console.log('No data received in WhatsApp webhook')
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
}
|
||||
|
||||
// Extract message details
|
||||
const messages = data.messages || []
|
||||
|
||||
if (messages.length === 0) {
|
||||
// This might be a different type of notification (e.g., status update)
|
||||
console.log('No messages in WhatsApp payload, might be a status update')
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
}
|
||||
|
||||
// Process each message
|
||||
for (const message of messages) {
|
||||
const phoneNumberId = data.metadata?.phone_number_id
|
||||
const from = message.from
|
||||
const messageId = message.id
|
||||
const timestamp = message.timestamp
|
||||
const text = message.text?.body
|
||||
|
||||
console.log(
|
||||
`Received WhatsApp message: ${text ? text.substring(0, 50) : '[no text]'} from ${from}`
|
||||
)
|
||||
|
||||
// Execute each matching workflow with the WhatsApp message data
|
||||
for (const { webhook: wh, workflow: wf } of webhooks) {
|
||||
const executionId = uuidv4()
|
||||
|
||||
try {
|
||||
// Get the workflow state
|
||||
if (!wf.state) {
|
||||
console.log(`Workflow ${wf.id} has no state, skipping`)
|
||||
continue
|
||||
}
|
||||
|
||||
console.log(`Executing workflow ${wf.id} for WhatsApp message ${messageId}`)
|
||||
|
||||
// Create input payload for the workflow
|
||||
const input = {
|
||||
whatsapp: {
|
||||
data: {
|
||||
messageId,
|
||||
from,
|
||||
phoneNumberId,
|
||||
text,
|
||||
timestamp,
|
||||
raw: message,
|
||||
},
|
||||
},
|
||||
webhook: {
|
||||
data: {
|
||||
provider: 'whatsapp',
|
||||
path: wh.path,
|
||||
providerConfig: wh.providerConfig,
|
||||
payload: body,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
method: request.method,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Get the workflow state
|
||||
const state = wf.state as any
|
||||
const { blocks, edges, loops } = state
|
||||
|
||||
// Use the same execution flow as in manual executions
|
||||
const mergedStates = mergeSubblockState(blocks)
|
||||
|
||||
// Retrieve environment variables for this user
|
||||
const [userEnv] = await db
|
||||
.select()
|
||||
.from(environment)
|
||||
.where(eq(environment.userId, wf.userId))
|
||||
.limit(1)
|
||||
|
||||
if (!userEnv) {
|
||||
console.log(`No environment variables found for user ${wf.userId}`)
|
||||
}
|
||||
|
||||
// Create a map of decrypted environment variables
|
||||
const decryptedEnvVars: Record<string, string> = {}
|
||||
if (userEnv) {
|
||||
for (const [key, encryptedValue] of Object.entries(
|
||||
userEnv.variables as Record<string, string>
|
||||
)) {
|
||||
try {
|
||||
const { decrypted } = await decryptSecret(encryptedValue)
|
||||
decryptedEnvVars[key] = decrypted
|
||||
} catch (error: any) {
|
||||
console.error(`Failed to decrypt ${key}:`, error)
|
||||
throw new Error(`Failed to decrypt environment variable "${key}": ${error.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Serialize and execute the workflow
|
||||
const serializedWorkflow = new Serializer().serializeWorkflow(
|
||||
mergedStates as any,
|
||||
edges,
|
||||
loops
|
||||
)
|
||||
|
||||
const executor = new Executor(
|
||||
serializedWorkflow,
|
||||
mergedStates as any,
|
||||
decryptedEnvVars,
|
||||
input
|
||||
)
|
||||
const result = await executor.execute(wf.id)
|
||||
|
||||
console.log(`Successfully executed workflow ${wf.id}`)
|
||||
|
||||
// Log each execution step
|
||||
for (const log of result.logs || []) {
|
||||
await persistLog({
|
||||
id: uuidv4(),
|
||||
workflowId: wf.id,
|
||||
executionId,
|
||||
level: log.success ? 'info' : 'error',
|
||||
message: log.success
|
||||
? `Block ${log.blockName || log.blockId} (${log.blockType}): ${JSON.stringify(log.output?.response || {})}`
|
||||
: `Block ${log.blockName || log.blockId} (${log.blockType}): ${log.error || 'Failed'}`,
|
||||
duration: log.success ? `${log.durationMs}ms` : 'NA',
|
||||
trigger: 'webhook',
|
||||
createdAt: new Date(log.endedAt || log.startedAt),
|
||||
})
|
||||
}
|
||||
|
||||
// Calculate total duration from successful block logs
|
||||
const totalDuration = (result.logs || [])
|
||||
.filter((log) => log.success)
|
||||
.reduce((sum, log) => sum + log.durationMs, 0)
|
||||
|
||||
// Log the final execution result
|
||||
await persistLog({
|
||||
id: uuidv4(),
|
||||
workflowId: wf.id,
|
||||
executionId,
|
||||
level: result.success ? 'info' : 'error',
|
||||
message: result.success
|
||||
? 'WhatsApp webhook executed successfully'
|
||||
: `WhatsApp webhook execution failed: ${result.error}`,
|
||||
duration: result.success ? `${totalDuration}ms` : 'NA',
|
||||
trigger: 'webhook',
|
||||
createdAt: new Date(),
|
||||
})
|
||||
} catch (error: any) {
|
||||
console.error(`Error executing workflow ${wf.id}:`, error)
|
||||
|
||||
// Log the error
|
||||
await persistLog({
|
||||
id: uuidv4(),
|
||||
workflowId: wf.id,
|
||||
executionId,
|
||||
level: 'error',
|
||||
message: `WhatsApp webhook execution failed: ${error.message || error}`,
|
||||
duration: 'NA',
|
||||
trigger: 'webhook',
|
||||
createdAt: new Date(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Always return a 200 OK to WhatsApp
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
} catch (error: any) {
|
||||
console.error('Error processing WhatsApp webhook:', error)
|
||||
// Still return 200 to prevent WhatsApp from retrying
|
||||
return new NextResponse('OK', { status: 200 })
|
||||
}
|
||||
}
|
||||
@@ -124,11 +124,7 @@ export function WebhookModal({
|
||||
? `${window.location.protocol}//${window.location.host}`
|
||||
: 'https://your-domain.com'
|
||||
|
||||
// Use the dedicated endpoint for WhatsApp, path-based for others
|
||||
const webhookUrl =
|
||||
webhookProvider === 'whatsapp'
|
||||
? `${baseUrl}/api/webhooks/whatsapp`
|
||||
: `${baseUrl}/api/webhooks/trigger/${formattedPath}`
|
||||
const webhookUrl = `${baseUrl}/api/webhooks/trigger/${formattedPath}`
|
||||
|
||||
const copyToClipboard = (text: string, type: string) => {
|
||||
navigator.clipboard.writeText(text)
|
||||
@@ -192,16 +188,8 @@ export function WebhookModal({
|
||||
setIsTesting(true)
|
||||
setTestResult(null)
|
||||
|
||||
// Use the provider-specific test endpoint
|
||||
let testEndpoint = `/api/webhooks/test/generic?id=${webhookId}`
|
||||
|
||||
if (webhookProvider === 'whatsapp') {
|
||||
testEndpoint = `/api/webhooks/test/whatsapp?id=${webhookId}`
|
||||
} else if (webhookProvider === 'github') {
|
||||
testEndpoint = `/api/webhooks/test/github?id=${webhookId}`
|
||||
} else if (webhookProvider === 'stripe') {
|
||||
testEndpoint = `/api/webhooks/test/stripe?id=${webhookId}`
|
||||
}
|
||||
// Use the consolidated test endpoint
|
||||
const testEndpoint = `/api/webhooks/test?id=${webhookId}`
|
||||
|
||||
const response = await fetch(testEndpoint)
|
||||
if (!response.ok) {
|
||||
@@ -214,8 +202,7 @@ export function WebhookModal({
|
||||
if (data.success) {
|
||||
setTestResult({
|
||||
success: true,
|
||||
message:
|
||||
data.message || 'Webhook configuration is valid. You can now use this URL in WhatsApp.',
|
||||
message: data.message || 'Webhook configuration is valid.',
|
||||
})
|
||||
} else {
|
||||
setTestResult({
|
||||
|
||||
Reference in New Issue
Block a user