import { serve } from "https://deno.land/std@0.168.0/http/server.ts"; import { createClient } from "https://esm.sh/@supabase/supabase-js@2.57.4"; import { edgeLogger } from '../_shared/logger.ts'; const corsHeaders = { 'Access-Control-Allow-Origin': '*', 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', }; // Simple request tracking const startRequest = () => ({ requestId: crypto.randomUUID(), start: Date.now() }); const endRequest = (tracking: { start: number }) => Date.now() - tracking.start; serve(async (req) => { const tracking = startRequest(); if (req.method === 'OPTIONS') { return new Response(null, { headers: corsHeaders }); } try { const supabaseUrl = Deno.env.get('SUPABASE_URL')!; const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!; const supabase = createClient(supabaseUrl, supabaseServiceKey); const event = await req.json(); edgeLogger.info('Received Novu webhook event', { action: 'novu_webhook', eventType: event.type, requestId: tracking.requestId }); // Handle different webhook events switch (event.type) { case 'notification.sent': await handleNotificationSent(supabase, event); break; case 'notification.delivered': await handleNotificationDelivered(supabase, event); break; case 'notification.read': await handleNotificationRead(supabase, event); break; case 'notification.failed': await handleNotificationFailed(supabase, event); break; default: edgeLogger.warn('Unhandled Novu event type', { action: 'novu_webhook', eventType: event.type, requestId: tracking.requestId }); } const duration = endRequest(tracking); return new Response( JSON.stringify({ success: true, requestId: tracking.requestId }), { headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId }, status: 200, } ); } catch (error: any) { const duration = endRequest(tracking); edgeLogger.error('Error processing webhook', { action: 'novu_webhook', error: error?.message, requestId: tracking.requestId, duration }); return new Response( JSON.stringify({ success: false, error: error.message, requestId: tracking.requestId }), { headers: { ...corsHeaders, 'Content-Type': 'application/json', 'X-Request-ID': tracking.requestId }, status: 500, } ); } }); async function handleNotificationSent(supabase: any, event: any) { const { transactionId, channel } = event.data; await supabase .from('notification_logs') .update({ status: 'sent' }) .eq('novu_transaction_id', transactionId); } async function handleNotificationDelivered(supabase: any, event: any) { const { transactionId } = event.data; await supabase .from('notification_logs') .update({ status: 'delivered', delivered_at: new Date().toISOString(), }) .eq('novu_transaction_id', transactionId); } async function handleNotificationRead(supabase: any, event: any) { const { transactionId } = event.data; await supabase .from('notification_logs') .update({ read_at: new Date().toISOString(), }) .eq('novu_transaction_id', transactionId); } async function handleNotificationFailed(supabase: any, event: any) { const { transactionId, error } = event.data; await supabase .from('notification_logs') .update({ status: 'failed', error_message: error, }) .eq('novu_transaction_id', transactionId); }