Skip to content

Conversation

@qubi00
Copy link
Collaborator

@qubi00 qubi00 commented Nov 11, 2025

Pull Request: Synced clerk user updates with supabase db

Type of Change

  • ✨ New feature

Description

Now all user interactions through clerk(sign up, change information, delete) will also be reflected in our Users table

How to Test / Verification Steps

  1. Sign up in the website
  2. View the Users table for the new/updated row

Screenshots / GIFs (if applicable)

image

Summary by CodeRabbit

  • New Features
    • Integrated Clerk webhooks to automatically sync user accounts on creation, updates, and deletions.
  • Bug Fixes / Reliability
    • Improved webhook validation and error handling for more reliable and secure user-sync processing.

@coderabbitai
Copy link

coderabbitai bot commented Nov 11, 2025

Walkthrough

Adds a new Next.js App Router POST route at app/api/webhooks/clerk/route.ts that verifies Svix-signed Clerk webhooks, handles user.created/user.updated/user.deleted events to upsert/delete records in Supabase, and adds the svix dependency.

Changes

Cohort / File(s) Summary
Clerk webhook route
app/api/webhooks/clerk/route.ts
New POST route handler: validates CLERK_WEBHOOK_SECRET, extracts svix-id, svix-timestamp, svix-signature headers, reads raw body, verifies signature via svix Webhook, parses Clerk event, handles user.created/user.updated (extracts id/email/first_name/last_name; upserts into Users via supabase_admin.from('Users')) and user.deleted (deletes by clerk_id), logs outcomes, and returns appropriate 4xx/5xx/200 responses.
Dependencies
package.json
Added runtime dependency svix ^1.81.0 for webhook signature verification.

Sequence Diagram

sequenceDiagram
    actor Clerk
    participant Handler as Clerk Webhook Handler
    participant Svix as Svix Webhook (verify)
    participant DB as Supabase (supabase_admin)

    Clerk->>Handler: POST /api/webhooks/clerk (svix headers + body)

    rect rgb(235,245,255)
    Note over Handler: Validation
    Handler->>Handler: Check CLERK_WEBHOOK_SECRET
    Handler->>Handler: Ensure svix headers present
    end

    rect rgb(245,255,235)
    Note over Handler,Svix: Signature verification
    Handler->>Svix: verify(body, headers)
    Svix-->>Handler: valid / invalid
    end

    alt valid
        Handler->>Handler: parse event.type & data
        alt user.created or user.updated
            Handler->>DB: upsert user (clerk_id, email, names)
            DB-->>Handler: success / error
        else user.deleted
            Handler->>DB: delete where clerk_id
            DB-->>Handler: success / warning
        end
        Handler-->>Clerk: 200 OK (Webhook processed)
    else invalid or missing
        Handler-->>Clerk: 400 Bad Request / 500 if env missing
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Verify correct use of svix Webhook API and raw-body handling for signature verification.
  • Check Supabase upsert/delete logic, queries, and error handling in app/api/webhooks/clerk/route.ts.
  • Confirm environment-variable behavior and explicit error responses/logging.

Pre-merge checks and finishing touches

✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Feature/clerk id sync' directly refers to the main change: implementing Clerk webhook synchronization with Supabase Users table.
Description check ✅ Passed The PR description includes the required template sections with Type of Change, Description, and How to Test verified, plus a screenshot. Most critical sections are complete.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/clerk_id-sync

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b4b905 and 7b554ff.

📒 Files selected for processing (1)
  • app/api/webhooks/clerk/route.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/api/webhooks/clerk/route.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (2)
app/api/webhooks/clerk/route.ts (2)

44-70: Consider extracting event handlers into separate functions.

The user creation/update logic is well-structured and handles errors appropriately. For improved testability and maintainability, consider extracting this into a separate async function (e.g., handleUserCreatedOrUpdated).

Example refactor:

async function handleUserCreatedOrUpdated(userId: string, eventData: any) {
  const { email_addresses, first_name, last_name } = eventData;
  const email = email_addresses[0]?.email_address;

  if (!userId || !email) {
    return new NextResponse('Error: Missing clerk id or email', { status: 400 });
  }

  const { error: upsertError } = await supabase_admin.from('Users').upsert(
    {
      clerk_id: userId,
      email: email,
      first_name: first_name,
      last_name: last_name,
    },
    {
      onConflict: 'clerk_id',
    }
  );

  if (upsertError) {
    console.error('Supabase upsert error:', upsertError.message);
    return new NextResponse('Error processing webhook', { status: 500 });
  }

  console.log('User was created or updated in Supabase.');
  return null; // Success
}

Then in the main handler:

if (eventType === 'user.created' || eventType === 'user.updated') {
  const response = await handleUserCreatedOrUpdated(id, wh_event.data);
  if (response) return response;
}

23-23: Remove self-evident comment.

The comment "new svix instance" is self-explanatory from the code itself.

Apply this diff:

-  // new svix instance
   const wh = new Webhook(WEBHOOK_SECRET);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0f2a0d5 and 4b4b905.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (2)
  • app/api/webhooks/clerk/route.ts (1 hunks)
  • package.json (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/api/webhooks/clerk/route.ts (1)
lib/supabase_admin.ts (1)
  • supabase_admin (13-15)
🔇 Additional comments (6)
app/api/webhooks/clerk/route.ts (5)

12-19: LGTM: Proper svix header extraction and validation.

The header extraction and validation correctly implements the svix webhook verification requirements. Returning 400 for missing headers is appropriate.


21-37: LGTM: Robust signature verification implementation.

The webhook signature verification correctly follows svix best practices: reading body as text, verifying with proper headers, and returning 400 for invalid signatures. Error logging aids debugging without blocking the response.


39-42: LGTM: Event data extraction is correct.

Extracting the clerk_id and event type from the verified webhook event is appropriate. The logging at this stage only includes the event type, which is good.


6-82: Overall implementation follows webhook best practices.

The webhook handler correctly implements:

  • Signature verification using svix
  • Idempotent database operations (upsert/delete)
  • Appropriate HTTP status codes for different error conditions
  • Event-specific handling logic

This provides a solid foundation for syncing Clerk user lifecycle events to Supabase.


60-60: Verify the unique constraint on clerk_id in your Supabase account.

The upsert operation in app/api/webhooks/clerk/route.ts (line 52-60) uses onConflict: 'clerk_id', which requires a unique constraint on the clerk_id column. The codebase doesn't version-control Supabase schema definitions—no SQL migrations or schema files are present in the repository. You must verify this constraint exists directly in your Supabase dashboard:

  1. Navigate to the Users table in Supabase
  2. Check the clerk_id column has a unique constraint defined

If the constraint is missing, the upsert will fail at runtime.

package.json (1)

30-30: svix version 1.81.0 is current and secure—no changes needed.

The latest version of svix is 1.81.0, which matches what's specified in package.json. GitHub's security vulnerability database shows no known vulnerabilities for the svix package. The caret constraint (^1.81.0) appropriately allows for future patch and minor updates.

return new NextResponse('Error processing webhook', { status: 500 });
}

console.log(`User ${id} was created or updated in Supabase.`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove user identifiers from logs to comply with privacy requirements.

Logging clerk_id (user identifier) creates compliance and privacy risks. Per security best practices, avoid logging user identifiers like emails, user IDs, etc., as they can constitute PII under GDPR/CCPA.

Apply this diff to remove identifiers from logs:

-    console.log(`User ${id} was created or updated in Supabase.`);
+    console.log('User was created or updated in Supabase.');
-    console.log(`User ${id} was deleted from Supabase.`);
+    console.log('User was deleted from Supabase.');

If you need to track specific user operations for debugging, consider using structured logging with appropriate PII redaction or filtering at the log aggregation layer.

Also applies to: 78-78

🤖 Prompt for AI Agents
In app/api/webhooks/clerk/route.ts around lines 69 and 78, the code logs the
Clerk user identifier (e.g., `id`) which can expose PII; remove the identifier
from the console logs and replace with a generic message or a non-PII debug
token. Update both log statements to avoid printing any user IDs/emails — e.g.,
log "A user was created or updated in Supabase" and "A user was deleted in
Supabase" or use structured logging that omits/redacts PII and emits only
non-identifying context; do not log the `id` value directly.

Comment on lines 74 to 76
if (deleteError) {
console.warn(`Supabase delete error (user ${id}):`, deleteError.message);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Consider the implications of continuing execution after delete errors.

Delete errors are logged as warnings but don't prevent the webhook from returning a 200 success response. While this approach provides idempotency (subsequent deletes won't cause retries), it could mask genuine database issues.

Consider whether you want to:

  1. Keep current behavior for idempotency (acceptable for deleted user scenarios)
  2. Return an error response for actual database failures

If keeping current behavior, add a comment explaining the rationale:

     if (deleteError) {
-      console.warn(`Supabase delete error (user ${id}):`, deleteError.message);
+      // Log as warning rather than returning error to maintain idempotency
+      // (subsequent deletes of already-deleted users should not cause retries)
+      console.warn('Supabase delete error:', deleteError.message);
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (deleteError) {
console.warn(`Supabase delete error (user ${id}):`, deleteError.message);
}
if (deleteError) {
// Log as warning rather than returning error to maintain idempotency
// (subsequent deletes of already-deleted users should not cause retries)
console.warn('Supabase delete error:', deleteError.message);
}
🤖 Prompt for AI Agents
In app/api/webhooks/clerk/route.ts around lines 74-76, the code currently logs
Supabase delete errors as warnings but still returns a 200 success response;
decide and implement one of two fixes: (A) if you want idempotency and to
intentionally suppress retries for deleted users, add a concise comment above
the warn explaining that database delete failures are intentionally non-fatal to
avoid webhook retries for already-deleted users and note when this should be
revisited (e.g., persistent failures), or (B) if you want to surface real DB
problems, change the flow to return an error response when deleteError is truthy
(respond with a 5xx and include error details in the response/log) so callers
know the webhook failed; apply only one of these and ensure the added comment or
changed response is clear and short.

@arnavSingh23 arnavSingh23 merged commit 78246e8 into dev Nov 11, 2025
1 of 2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 11, 2025
19 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants