The Complete Guide to Supabase Webhooks: Building a reliable Payment & Auth Context with RBAC PART-1

~What This Guide Covers (Important Context)

 Before we get deep into Supabase webhooks, it's mandatory to understand the scope of what we're building together. This guide focuses on the internal webhook processing system that handles events after they've already been received and validated by your server. We're not covering the initial external webhook setup from payment providers like Stripe, PayPal, or authentication services - that's assumed to be already configured.

 Think of it this way: when Stripe sends a webhook to your /api/webhooks/stripe endpoint about a successful payment, your external webhook handler might look something like this:

1// app/api/webhooks/stripe/route.ts - EXTERNAL webhook (assumed already implemented)
2export async function POST(request: NextRequest) {
3  const sig = headers().get('stripe-signature');
4  const body = await request.text();
5  
6  // Verify Stripe webhook signature
7  const event = stripe.webhooks.constructEvent(body, sig!, process.env.STRIPE_WEBHOOK_SECRET!);
8  
9  // Update your database based on the Stripe event
10  if (event.type === 'payment_intent.succeeded') {
11    await supabase
12      .from('payments')
13      .update({ status: 'succeeded' })
14      .eq('stripe_payment_id', event.data.object.id);
15  }
16  
17  return NextResponse.json({ received: true });
18}

 What happens next is where our guide begins. When that Supabase database update occurs (changing the payment status), it triggers a Supabase webhook - an internal system that responds to your own database changes. The entire system we'll build together handles these internal Supabase-generated webhooks, creating a reliable event-driven architecture that automatically manages user permissions, subscription tiers, and business logic based on your database state changes.

 This approach gives you the best of both worlds: external webhooks handle third-party integrations, while internal Supabase webhooks manage your application's business logic and state synchronization.

The Business Problem We're Solving

 Supabase webhooks are one of the most powerful features for building real-time, event-driven applications. They allow your application to react instantly to database changes, user authentication events, and custom triggers without constantly polling your database. In this comprehensive guide, we'll build a complete payment and authentication system with Role-Based Access Control (RBAC) using Next.js 15+ App Router, TypeScript, and Supabase webhooks.

 Our specific challenges:

  • Users have different subscription tiers (Free, Pro, Enterprise)
  • Each tier has different permissions (create projects, invite team members etc.)
  • When someone upgrades/downgrades, their access should change immediately
  • When payment fails, they should be downgraded automatically

 Webhooks work by sending HTTP POST requests to your specified endpoint whenever certain events occur in your Supabase project. Think of them as automated messengers that notify your application about important changes. Whether a user signs up, a payment is processed, or data is modified, webhooks ensure your application stays in perfect sync with your database state. This real-time synchronization is mandatory for modern applications where users expect immediate feedback and smooth experiences.

Understanding Supabase Webhook Architecture

 Before getting into code, let's understand how Supabase webhooks operate under the hood. When you create a webhook in Supabase, you're essentially creating a database trigger that fires whenever specific conditions are met. These triggers can be configured to activate on INSERT, UPDATE, DELETE, or even custom events. The webhook system then takes the triggered data, formats it into a structured payload, and sends it to your designated endpoint via HTTP POST request.

Step 1

1graph TD
2    A[Database Operation] --> B{Operation Type}
3    B -->|INSERT| C[Insert Trigger]
4    B -->|UPDATE| D[Update Trigger] 
5    B -->|DELETE| E[Delete Trigger]
6    B -->|Custom Event| F[Custom Trigger]
7    
8    C --> G[Trigger Fires]
9    D --> G
10    E --> G
11    F --> G
12    
13    style A fill:#333333
14    style G fill:#333000

Step 2

1graph TD
2    A[Trigger Fired] --> B[Webhook System Activated]
3    B --> C[Data Collection]
4    
5    C --> D[Payload Assembly]
6    D --> E[Changed Data]
7    D --> F[User Information]
8    D --> G[Timestamps]
9    D --> H[Operation Type]
10    D --> I[Metadata]
11    
12    E --> J[Complete Payload]
13    F --> J
14    G --> J
15    H --> J
16    I --> J
17    
18    style A fill:#333333
19    style B fill:#333333
20    style J fill:#333000

Step 3

1graph TD
2    A[Complete Payload] --> B[Payload Validation]
3    B --> C{Valid?}
4    C -->|No| D[Log Error]
5    C -->|Yes| E[HTTP POST Request]
6    
7    E --> F[Send to Endpoint]
8    F --> G{Response Status}
9    
10    G -->|Success 2xx| H[Log Success]
11    G -->|Failure 4xx/5xx| I[Retry Mechanism]
12    
13    I --> J{Retry Count < Max?}
14    J -->|Yes| K[Wait & Retry]
15    J -->|No| L[Log Final Failure]
16    
17    K --> F
18    
19    style A fill:#333333
20    style E fill:#333333
21    style H fill:#333333
22    style L fill:#333000

 The beauty of Supabase webhooks lies in their reliability and flexibility. They include built-in retry mechanisms, payload validation, and detailed logging. Each webhook payload contains not only the changed data but also metadata about the operation, including the user who made the change, timestamps, and the type of operation performed. This rich context allows you to build sophisticated business logic that responds appropriately to different scenarios.

1// types/webhook.ts
2export interface SupabaseWebhookPayload<T = any> {
3  type: 'INSERT' | 'UPDATE' | 'DELETE';
4  table: string;
5  schema: string;
6  record: T | null;
7  old_record: T | null;
8  created_at: string;
9  user_id?: string;
10}
11
12export interface WebhookAuthPayload {
13  type: 'user.created' | 'user.updated' | 'user.deleted' | 'user.signed_in';
14  user: {
15    id: string;
16    email: string;
17    created_at: string;
18    updated_at: string;
19    user_metadata: Record<string, any>;
20    app_metadata: Record<string, any>;
21  };
22  created_at: string;
23}

Setting Up Your Next.js 15+ Environment

 The foundation of our webhook system starts with a properly configured Next.js application. Next.js 15+ with the App Router provides excellent TypeScript support and makes it easy to create API routes that can handle webhook requests. We'll structure our application to separate concerns clearly, with dedicated handlers for different types of webhook events.

 Our project structure will include dedicated API routes for handling webhooks, TypeScript types for strong typing, utility functions for common operations, and React components that respond to real-time changes. The App Router's file-based routing system makes it intuitive to organize webhook endpoints, and the built-in middleware support allows us to add authentication and validation layers easily.

1// app/api/webhooks/supabase/route.ts
2import { NextRequest, NextResponse } from 'next/server';
3import { headers } from 'next/headers';
4import { createServerClient } from '@supabase/ssr';
5import { SupabaseWebhookPayload } from '@/types/webhook';
6
7export async function POST(request: NextRequest) {
8  try {
9    // Verify the webhook signature
10    const headersList = headers();
11    const signature = headersList.get('x-supabase-signature');
12    const timestamp = headersList.get('x-supabase-timestamp');
13    
14    if (!signature || !timestamp) {
15      return NextResponse.json(
16        { error: 'Missing webhook signature or timestamp' },
17        { status: 401 }
18      );
19    }
20
21    // Parse the webhook payload
22    const payload: SupabaseWebhookPayload = await request.json();
23    
24    // Create Supabase client for server-side operations
25    const supabase = createServerClient(
26      process.env.NEXT_PUBLIC_SUPABASE_URL!,
27      process.env.SUPABASE_SERVICE_KEY!,
28      {
29        cookies: {
30          get: () => '',
31          set: () => {},
32          remove: () => {},
33        },
34      }
35    );
36
37    // Route to appropriate handler based on table and type
38    await handleWebhookEvent(payload, supabase);
39
40    return NextResponse.json({ success: true });
41  } catch (error) {
42    console.error('Webhook processing error:', error);
43    return NextResponse.json(
44      { error: 'Webhook processing failed' },
45      { status: 500 }
46    );
47  }
48}

Implementing Webhook Security and Validation

 Security is paramount when handling webhooks, as they create public endpoints that external services can access. Supabase provides webhook signatures that allow you to verify that requests are genuinely coming from your Supabase instance and haven't been tampered with during transmission. This verification process involves comparing a hash of the payload with the signature provided in the request headers.

 The signature verification uses HMAC-SHA256 encryption with your webhook secret key. This cryptographic approach ensures that only requests with the correct signature (which requires knowledge of your secret key) can be processed. Additionally, implementing timestamp validation prevents replay attacks where malicious actors might attempt to resend old webhook requests.

1// lib/webhook-security.ts
2import crypto from 'crypto';
3
4export function verifyWebhookSignature(
5  payload: string,
6  signature: string,
7  timestamp: string,
8  secret: string
9): boolean {
10  try {
11    // Create the expected signature
12    const expectedSignature = crypto
13      .createHmac('sha256', secret)
14      .update(timestamp + '.' + payload)
15      .digest('hex');
16
17    // Compare signatures using a constant-time comparison
18    return crypto.timingSafeEqual(
19      Buffer.from(signature, 'hex'),
20      Buffer.from(expectedSignature, 'hex')
21    );
22  } catch (error) {
23    console.error('Signature verification error:', error);
24    return false;
25  }
26}
27
28export function validateTimestamp(timestamp: string, toleranceSeconds = 300): boolean {
29  const webhookTime = parseInt(timestamp, 10) * 1000; // Convert to milliseconds
30  const currentTime = Date.now();
31  const timeDifference = Math.abs(currentTime - webhookTime);
32  
33  return timeDifference <= toleranceSeconds * 1000;
34}
35
36// Enhanced webhook route with security
37export async function POST(request: NextRequest) {
38  try {
39    const body = await request.text();
40    const headersList = headers();
41    const signature = headersList.get('x-supabase-signature');
42    const timestamp = headersList.get('x-supabase-timestamp');
43
44    // Validate required headers
45    if (!signature || !timestamp) {
46      return NextResponse.json(
47        { error: 'Missing required webhook headers' },
48        { status: 401 }
49      );
50    }
51
52    // Verify timestamp to prevent replay attacks
53    if (!validateTimestamp(timestamp)) {
54      return NextResponse.json(
55        { error: 'Webhook timestamp is too old' },
56        { status: 401 }
57      );
58    }
59
60    // Verify webhook signature
61    if (!verifyWebhookSignature(body, signature, timestamp, process.env.SUPABASE_WEBHOOK_SECRET!)) {
62      return NextResponse.json(
63        { error: 'Invalid webhook signature' },
64        { status: 401 }
65      );
66    }
67
68    const payload: SupabaseWebhookPayload = JSON.parse(body);
69    await handleWebhookEvent(payload);
70
71    return NextResponse.json({ success: true });
72  } catch (error) {
73    console.error('Webhook error:', error);
74    return NextResponse.json(
75      { error: 'Internal server error' },
76      { status: 500 }
77    );
78  }
79}

Building the Payment Context System

 Payment processing requires careful orchestration of multiple systems - your payment provider, your database, and your user interface. Webhooks play a mandatory role in this ecosystem by ensuring that payment status changes are immediately reflected throughout your application. When a payment succeeds, fails, or requires additional verification, your webhook system can update user records, send notifications, and trigger business logic automatically.

 Our payment context system will handle subscription management, one-time payments, refunds, and payment method updates. Each payment event triggers specific actions that maintain data consistency and provide users with immediate feedback. The system also implements idempotency to handle duplicate webhook calls gracefully, which is essential for financial transactions where accuracy is critical.

1// types/payment.ts
2export interface PaymentRecord {
3  id: string;
4  user_id: string;
5  amount: number;
6  currency: string;
7  status: 'pending' | 'succeeded' | 'failed' | 'canceled' | 'refunded';
8  payment_method: string;
9  subscription_id?: string;
10  created_at: string;
11  updated_at: string;
12  metadata: Record<string, any>;
13}
14
15export interface SubscriptionRecord {
16  id: string;
17  user_id: string;
18  plan_id: string;
19  status: 'active' | 'inactive' | 'canceled' | 'past_due';
20  current_period_start: string;
21  current_period_end: string;
22  cancel_at_period_end: boolean;
23  created_at: string;
24  updated_at: string;
25}
26
27// lib/payment-handler.ts
28import { createServerClient } from '@supabase/ssr';
29import { PaymentRecord, SubscriptionRecord } from '@/types/payment';
30
31export class PaymentWebhookHandler {
32  private supabase;
33
34  constructor(supabaseUrl: string, serviceKey: string) {
35    this.supabase = createServerClient(supabaseUrl, serviceKey, {
36      cookies: { get: () => '', set: () => {}, remove: () => {} },
37    });
38  }
39
40  async handlePaymentEvent(payload: SupabaseWebhookPayload<PaymentRecord>) {
41    const { type, record, old_record } = payload;
42
43    switch (type) {
44      case 'INSERT':
45        await this.handleNewPayment(record!);
46        break;
47      case 'UPDATE':
48        await this.handlePaymentUpdate(record!, old_record!);
49        break;
50      case 'DELETE':
51        await this.handlePaymentDeletion(old_record!);
52        break;
53    }
54  }
55
56  private async handleNewPayment(payment: PaymentRecord) {
57    console.log(`New payment created: ${payment.id} for user ${payment.user_id}`);
58    
59    // Update user's payment history
60    await this.updateUserPaymentStats(payment.user_id, payment);
61    
62    // Send confirmation email if payment succeeded
63    if (payment.status === 'succeeded') {
64      await this.sendPaymentConfirmation(payment);
65    }
66    
67    // Handle subscription-related payments
68    if (payment.subscription_id) {
69      await this.updateSubscriptionStatus(payment);
70    }
71  }
72
73  private async handlePaymentUpdate(payment: PaymentRecord, oldPayment: PaymentRecord) {
74    // Check if payment status changed
75    if (payment.status !== oldPayment.status) {
76      console.log(`Payment ${payment.id} status changed from ${oldPayment.status} to ${payment.status}`);
77      
78      switch (payment.status) {
79        case 'succeeded':
80          await this.activateUserServices(payment.user_id);
81          await this.sendPaymentConfirmation(payment);
82          break;
83        case 'failed':
84          await this.handlePaymentFailure(payment);
85          break;
86        case 'refunded':
87          await this.handleRefund(payment);
88          break;
89      }
90    }
91  }
92
93  private async updateUserPaymentStats(userId: string, payment: PaymentRecord) {
94    const { data: user } = await this.supabase
95      .from('user_profiles')
96      .select('payment_stats')
97      .eq('id', userId)
98      .single();
99
100    const currentStats = user?.payment_stats || {
101      total_payments: 0,
102      total_amount: 0,
103      last_payment_at: null,
104    };
105
106    const updatedStats = {
107      total_payments: currentStats.total_payments + 1,
108      total_amount: currentStats.total_amount + payment.amount,
109      last_payment_at: payment.created_at,
110    };
111
112    await this.supabase
113      .from('user_profiles')
114      .update({ payment_stats: updatedStats })
115      .eq('id', userId);
116  }
117}

Implementing Role-Based Access Control (RBAC)

 RBAC systems become incredibly powerful when combined with webhooks because they can automatically adjust user permissions based on real-time events. When a user's subscription expires, their role can be automatically downgraded. When they upgrade their plan, new permissions can be instantly granted. This automation ensures that your application's security model stays perfectly synchronized with your business logic.

 Our RBAC implementation will use hierarchical roles where higher-level roles inherit permissions from lower levels. This approach simplifies permission management while providing fine-grained control over user capabilities. The webhook system will monitor role changes, permission updates, and subscription status changes to maintain accurate access control throughout your application.

1// types/rbac.ts
2export interface Role {
3  id: string;
4  name: string;
5  description: string;
6  level: number; // Higher numbers indicate more permissions
7  permissions: Permission[];
8  created_at: string;
9  updated_at: string;
10}
11
12export interface Permission {
13  id: string;
14  name: string;
15  resource: string;
16  action: string; // 'create', 'read', 'update', 'delete'
17  conditions?: Record<string, any>;
18}
19
20export interface UserRole {
21  id: string;
22  user_id: string;
23  role_id: string;
24  assigned_at: string;
25  assigned_by: string;
26  expires_at?: string;
27  is_active: boolean;
28}
29
30// lib/rbac-handler.ts
31export class RBACWebhookHandler {
32  private supabase;
33
34  constructor(supabaseUrl: string, serviceKey: string) {
35    this.supabase = createServerClient(supabaseUrl, serviceKey, {
36      cookies: { get: () => '', set: () => {}, remove: () => {} },
37    });
38  }
39
40  async handleUserRoleChange(payload: SupabaseWebhookPayload<UserRole>) {
41    const { type, record, old_record } = payload;
42
43    switch (type) {
44      case 'INSERT':
45        await this.handleRoleAssignment(record!);
46        break;
47      case 'UPDATE':
48        await this.handleRoleUpdate(record!, old_record!);
49        break;
50      case 'DELETE':
51        await this.handleRoleRevocation(old_record!);
52        break;
53    }
54  }
55
56  private async handleRoleAssignment(userRole: UserRole) {
57    // Get the role details
58    const { data: role } = await this.supabase
59      .from('roles')
60      .select('*, permissions(*)')
61      .eq('id', userRole.role_id)
62      .single();
63
64    if (!role) {
65      console.error(`Role ${userRole.role_id} not found`);
66      return;
67    }
68
69    // Update user's permission cache
70    await this.updateUserPermissions(userRole.user_id);
71    
72    // Log the role assignment
73    await this.logRoleChange({
74      user_id: userRole.user_id,
75      action: 'role_assigned',
76      role_name: role.name,
77      assigned_by: userRole.assigned_by,
78      timestamp: userRole.assigned_at,
79    });
80
81    // Send notification to user
82    await this.notifyRoleChange(userRole.user_id, 'assigned', role.name);
83  }
84
85  private async updateUserPermissions(userId: string) {
86    // Get all active roles for the user
87    const { data: userRoles } = await this.supabase
88      .from('user_roles')
89      .select(`
90        role_id,
91        roles (
92          name,
93          level,
94          permissions (*)
95        )
96      `)
97      .eq('user_id', userId)
98      .eq('is_active', true)
99      .or(`expires_at.is.null,expires_at.gt.${new Date().toISOString()}`);
100
101    if (!userRoles) return;
102
103    // Aggregate all permissions from active roles
104    const allPermissions = new Set<string>();
105    let highestRoleLevel = 0;
106
107    userRoles.forEach(userRole => {
108      const role = userRole.roles as any;
109      if (role.level > highestRoleLevel) {
110        highestRoleLevel = role.level;
111      }
112      
113      role.permissions.forEach((permission: Permission) => {
114        allPermissions.add(`${permission.resource}:${permission.action}`);
115      });
116    });
117
118    // Update user's permission cache in the database
119    await this.supabase
120      .from('user_profiles')
121      .update({
122        permissions: Array.from(allPermissions),
123        role_level: highestRoleLevel,
124        permissions_updated_at: new Date().toISOString(),
125      })
126      .eq('id', userId);
127  }
128
129  async checkPermission(userId: string, resource: string, action: string): Promise<boolean> {
130    const { data: user } = await this.supabase
131      .from('user_profiles')
132      .select('permissions, role_level')
133      .eq('id', userId)
134      .single();
135
136    if (!user) return false;
137
138    const requiredPermission = `${resource}:${action}`;
139    return user.permissions?.includes(requiredPermission) || false;
140  }
141}

Creating Comprehensive Webhook Handlers

 A reliable webhook system requires handlers that can process various types of events efficiently and reliably. Our comprehensive handler system will route different webhook types to appropriate processors, implement retry logic for failed operations, and maintain detailed logs for debugging and monitoring. This modular approach makes it easy to add new webhook types and modify existing behavior without affecting other parts of the system.

 The handler system implements the Command pattern, where each webhook type has a dedicated handler class that knows how to process that specific type of event. This separation of concerns makes the code more maintainable and testable. Additionally, we'll implement a queue system for processing webhooks asynchronously, which is mandatory for handling high-volume webhook traffic without blocking your application.

1// lib/webhook-dispatcher.ts
2import { PaymentWebhookHandler } from './payment-handler';
3import { RBACWebhookHandler } from './rbac-handler';
4import { AuthWebhookHandler } from './auth-handler';
5import { SupabaseWebhookPayload, WebhookAuthPayload } from '@/types/webhook';
6
7export class WebhookDispatcher {
8  private paymentHandler: PaymentWebhookHandler;
9  private rbacHandler: RBACWebhookHandler;
10  private authHandler: AuthWebhookHandler;
11
12  constructor() {
13    const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
14    const serviceKey = process.env.SUPABASE_SERVICE_KEY!;
15
16    this.paymentHandler = new PaymentWebhookHandler(supabaseUrl, serviceKey);
17    this.rbacHandler = new RBACWebhookHandler(supabaseUrl, serviceKey);
18    this.authHandler = new AuthWebhookHandler(supabaseUrl, serviceKey);
19  }
20
21  async dispatch(payload: SupabaseWebhookPayload | WebhookAuthPayload) {
22    try {
23      // Determine the webhook type and route accordingly
24      if ('table' in payload) {
25        await this.handleDatabaseWebhook(payload);
26      } else {
27        await this.handleAuthWebhook(payload);
28      }
29    } catch (error) {
30      console.error('Webhook dispatch error:', error);
31      await this.handleWebhookError(payload, error);
32      throw error; // Re-throw to ensure proper HTTP status code
33    }
34  }
35
36  private async handleDatabaseWebhook(payload: SupabaseWebhookPayload) {
37    const { table } = payload;
38
39    switch (table) {
40      case 'payments':
41        await this.paymentHandler.handlePaymentEvent(payload);
42        break;
43      case 'subscriptions':
44        await this.paymentHandler.handleSubscriptionEvent(payload);
45        break;
46      case 'user_roles':
47        await this.rbacHandler.handleUserRoleChange(payload);
48        break;
49      case 'roles':
50        await this.rbacHandler.handleRoleDefinitionChange(payload);
51        break;
52      case 'user_profiles':
53        await this.handleUserProfileChange(payload);
54        break;
55      default:
56        console.warn(`Unhandled table webhook: ${table}`);
57    }
58  }
59
60  private async handleAuthWebhook(payload: WebhookAuthPayload) {
61    const { type } = payload;
62
63    switch (type) {
64      case 'user.created':
65        await this.authHandler.handleUserCreated(payload);
66        break;
67      case 'user.updated':
68        await this.authHandler.handleUserUpdated(payload);
69        break;
70      case 'user.signed_in':
71        await this.authHandler.handleUserSignedIn(payload);
72        break;
73      case 'user.deleted':
74        await this.authHandler.handleUserDeleted(payload);
75        break;
76      default:
77        console.warn(`Unhandled auth webhook: ${type}`);
78    }
79  }
80
81  private async handleWebhookError(payload: any, error: any) {
82    // Log the error for debugging
83    console.error('Webhook processing failed:', {
84      payload,
85      error: error.message,
86      stack: error.stack,
87      timestamp: new Date().toISOString(),
88    });
89
90    // Store failed webhook for retry processing
91    await this.storeFailedWebhook(payload, error);
92  }
93
94  private async storeFailedWebhook(payload: any, error: any) {
95    // Implementation would store failed webhooks in a queue for retry
96    // This could use a database table, Redis queue, or message queue service
97  }
98}
99
100// Main webhook handler function
101export async function handleWebhookEvent(payload: SupabaseWebhookPayload | WebhookAuthPayload) {
102  const dispatcher = new WebhookDispatcher();
103  await dispatcher.dispatch(payload);
104}

Authentication Event Handling

 Authentication events are among the most critical webhooks to handle correctly because they directly impact user experience and security. When users sign up, sign in, update their profiles, or delete their accounts, your application needs to respond immediately with appropriate actions. These might include creating user profiles, sending welcome emails, updating activity logs, or cleaning up associated data.

 Our authentication webhook handler will create comprehensive user profiles when new users register, track login patterns for security monitoring, synchronize user data across different systems, and ensure proper cleanup when users delete their accounts. The system also implements security features like detecting suspicious login patterns and automatically locking accounts when necessary.

1// lib/auth-handler.ts
2export class AuthWebhookHandler {
3  private supabase;
4
5  constructor(supabaseUrl: string, serviceKey: string) {
6    this.supabase = createServerClient(supabaseUrl, serviceKey, {
7      cookies: { get: () => '', set: () => {}, remove: () => {} },
8    });
9  }
10
11  async handleUserCreated(payload: WebhookAuthPayload) {
12    const { user } = payload;
13    
14    try {
15      // Create comprehensive user profile
16      await this.createUserProfile(user);
17      
18      // Assign default role
19      await this.assignDefaultRole(user.id);
20      
21      // Send welcome email
22      await this.sendWelcomeEmail(user);
23      
24      // Initialize user preferences
25      await this.initializeUserPreferences(user.id);
26      
27      console.log(`User profile created for ${user.email}`);
28    } catch (error) {
29      console.error('Error handling user creation:', error);
30      throw error;
31    }
32  }
33
34  private async createUserProfile(user: any) {
35    const profileData = {
36      id: user.id,
37      email: user.email,
38      full_name: user.user_metadata?.full_name || '',
39      avatar_url: user.user_metadata?.avatar_url || '',
40      created_at: user.created_at,
41      updated_at: user.updated_at,
42      email_verified: user.email_confirmed_at ? true : false,
43      phone_verified: user.phone_confirmed_at ? true : false,
44      last_sign_in_at: null,
45      sign_in_count: 0,
46      preferences: {
47        theme: 'light',
48        notifications: {
49          email: true,
50          push: false,
51          marketing: false,
52        },
53        privacy: {
54          profile_visible: false,
55          show_email: false,
56        },
57      },
58      metadata: user.user_metadata || {},
59    };
60
61    const { error } = await this.supabase
62      .from('user_profiles')
63      .insert(profileData);
64
65    if (error) {
66      console.error('Error creating user profile:', error);
67      throw error;
68    }
69  }
70
71  private async assignDefaultRole(userId: string) {
72    // Get the default role (e.g., 'user' role)
73    const { data: defaultRole } = await this.supabase
74      .from('roles')
75      .select('id')
76      .eq('name', 'user')
77      .single();
78
79    if (!defaultRole) {
80      console.error('Default role not found');
81      return;
82    }
83
84    // Assign the default role to the user
85    const { error } = await this.supabase
86      .from('user_roles')
87      .insert({
88        user_id: userId,
89        role_id: defaultRole.id,
90        assigned_by: 'system',
91        assigned_at: new Date().toISOString(),
92        is_active: true,
93      });
94
95    if (error) {
96      console.error('Error assigning default role:', error);
97      throw error;
98    }
99  }
100
101  async handleUserSignedIn(payload: WebhookAuthPayload) {
102    const { user } = payload;
103    
104    try {
105      // Update last sign-in timestamp and increment counter
106      await this.updateSignInStats(user.id);
107      
108      // Check for suspicious activity
109      await this.checkSuspiciousActivity(user.id);
110      
111      // Update user's online status
112      await this.updateUserStatus(user.id, 'online');
113      
114      console.log(`User ${user.email} signed in`);
115    } catch (error) {
116      console.error('Error handling user sign-in:', error);
117    }
118  }
119
120  private async updateSignInStats(userId: string) {
121    const { data: profile } = await this.supabase
122      .from('user_profiles')
123      .select('sign_in_count')
124      .eq('id', userId)
125      .single();
126
127    const currentCount = profile?.sign_in_count || 0;
128
129    await this.supabase
130      .from('user_profiles')
131      .update({
132        last_sign_in_at: new Date().toISOString(),
133        sign_in_count: currentCount + 1,
134      })
135      .eq('id', userId);
136  }
137
138  private async checkSuspiciousActivity(userId: string) {
139    // Get recent sign-in attempts
140    const { data: recentSignIns } = await this.supabase
141      .from('auth_logs')
142      .select('*')
143      .eq('user_id', userId)
144      .eq('action', 'sign_in')
145      .gte('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString())
146      .order('created_at', { ascending: false })
147      .limit(10);
148
149    if (recentSignIns && recentSignIns.length > 5) {
150      // More than 5 sign-ins in 24 hours - potentially suspicious
151      await this.flagSuspiciousActivity(userId, 'frequent_logins');
152    }
153  }
154
155  async handleUserDeleted(payload: WebhookAuthPayload) {
156    const { user } = payload;
157    
158    try {
159      // Clean up user data while preserving audit trails
160      await this.cleanupUserData(user.id);
161      
162      // Anonymize rather than delete for compliance
163      await this.anonymizeUserData(user.id);
164      
165      console.log(`User ${user.id} data cleaned up`);
166    } catch (error) {
167      console.error('Error handling user deletion:', error);
168    }
169  }
170
171  private async cleanupUserData(userId: string) {
172    // Remove personal data but keep audit trails
173    await this.supabase
174      .from('user_profiles')
175      .update({
176        email: `deleted_user_${userId}@deleted.local`,
177        full_name: 'Deleted User',
178        avatar_url: null,
179        deleted_at: new Date().toISOString(),
180      })
181      .eq('id', userId);
182
183    // Deactivate all roles
184    await this.supabase
185      .from('user_roles')
186      .update({ is_active: false })
187      .eq('user_id', userId);
188  }
189}

 Part 2 is UP!