Initial wws serverless functions setup
- Add health check API endpoint (/api) - Add chat proxy to n8n workflow (/chat) - Add webhook receiver for external integrations (/webhook) - Add Dockerfile for container deployment - Add wws.toml configuration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
99
webhook/index.js
Normal file
99
webhook/index.js
Normal file
@@ -0,0 +1,99 @@
|
||||
// Webhook receiver for external integrations
|
||||
// Path: /webhook
|
||||
|
||||
addEventListener('fetch', event => {
|
||||
event.respondWith(handleRequest(event.request))
|
||||
})
|
||||
|
||||
async function handleRequest(request) {
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Webhook-Secret',
|
||||
}
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: corsHeaders })
|
||||
}
|
||||
|
||||
const url = new URL(request.url)
|
||||
const path = url.pathname
|
||||
|
||||
// Log incoming webhook
|
||||
const logEntry = {
|
||||
timestamp: new Date().toISOString(),
|
||||
method: request.method,
|
||||
path: path,
|
||||
headers: Object.fromEntries(request.headers.entries()),
|
||||
query: Object.fromEntries(url.searchParams.entries())
|
||||
}
|
||||
|
||||
if (request.method === 'POST') {
|
||||
try {
|
||||
const contentType = request.headers.get('content-type') || ''
|
||||
|
||||
let payload
|
||||
if (contentType.includes('application/json')) {
|
||||
payload = await request.json()
|
||||
} else if (contentType.includes('form')) {
|
||||
payload = Object.fromEntries(await request.formData())
|
||||
} else {
|
||||
payload = await request.text()
|
||||
}
|
||||
|
||||
logEntry.payload = payload
|
||||
|
||||
// Route based on webhook type/source
|
||||
const source = url.searchParams.get('source') || 'unknown'
|
||||
|
||||
// Forward to appropriate n8n workflow based on source
|
||||
const webhookMap = {
|
||||
'gitea': 'https://n8n.mylder.io/webhook/gitea-event',
|
||||
'stripe': 'https://n8n.mylder.io/webhook/stripe-event',
|
||||
'supabase': 'https://n8n.mylder.io/webhook/supabase-event',
|
||||
'default': 'https://n8n.mylder.io/webhook/generic-event'
|
||||
}
|
||||
|
||||
const targetUrl = webhookMap[source] || webhookMap['default']
|
||||
|
||||
// Async forward (fire and forget for speed)
|
||||
fetch(targetUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
source,
|
||||
payload,
|
||||
received_at: logEntry.timestamp,
|
||||
headers: logEntry.headers
|
||||
})
|
||||
}).catch(err => console.error('Forward failed:', err))
|
||||
|
||||
return new Response(JSON.stringify({
|
||||
received: true,
|
||||
source,
|
||||
timestamp: logEntry.timestamp
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
})
|
||||
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({
|
||||
received: false,
|
||||
error: error.message
|
||||
}), {
|
||||
status: 400,
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GET request - return webhook info
|
||||
return new Response(JSON.stringify({
|
||||
endpoint: '/webhook',
|
||||
method: 'POST',
|
||||
supported_sources: ['gitea', 'stripe', 'supabase', 'default'],
|
||||
usage: 'POST /webhook?source=gitea with JSON body'
|
||||
}), {
|
||||
headers: { 'Content-Type': 'application/json', ...corsHeaders }
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user