Ticket Hub uses Clerk for authentication and user management, with automatic synchronization to Convex database.
Prerequisites
Clerk account (clerk.com )
Next.js application
Convex backend configured
Environment Variables
CLERK_SECRET_KEY = sk_live_...
CLERK_WEBHOOK_SECRET = whsec_...
Convex Authentication Config
Configure Clerk as an authentication provider in Convex:
export default {
providers: [
{
domain: "https://your-clerk-instance.clerk.accounts.dev" ,
applicationID: "convex" ,
},
] ,
} ;
Replace your-clerk-instance with your actual Clerk instance domain from your Clerk dashboard.
Middleware Protection
Protect routes using Clerk middleware:
import { clerkMiddleware } from "@clerk/nextjs/server" ;
export default clerkMiddleware () ;
export const config = {
matcher: [
// Skip Next.js internals and static files
"/((?!_next|[^?]* \\ .(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)" ,
// Always run for API routes
"/(api|trpc)(.*)" ,
],
};
This configuration:
Applies authentication to all routes except static assets
Always protects API routes
Allows public access by default (customize as needed)
Webhook Setup
Sync user data from Clerk to Convex using webhooks:
import type { WebhookEvent } from "@clerk/backend" ;
import { httpRouter } from "convex/server" ;
import { Webhook } from "svix" ;
import { internal } from "./_generated/api" ;
import { httpAction } from "./_generated/server" ;
const http = httpRouter ();
http . route ({
path: "/clerk-users-webhook" ,
method: "POST" ,
handler: httpAction ( async ( ctx , request ) => {
const event = await validateRequest ( request );
if ( ! event ) {
return new Response ( "Error occured" , { status: 400 });
}
switch ( event . type ) {
case "user.created" : // intentional fallthrough
case "user.updated" :
await ctx . runMutation ( internal . users . upsertUserFromClerk , {
data: event . data ,
});
break ;
case "user.deleted" : {
const clerkUserId = event . data . id ! ;
await ctx . runMutation ( internal . users . deleteUser , {
userId: clerkUserId ,
});
break ;
}
default :
console . log ( "Ignored Clerk webhook event" , event . type );
}
return new Response ( null , { status: 200 });
}),
});
async function validateRequest ( req : Request ) : Promise < WebhookEvent | null > {
const payloadString = await req . text ();
const svixHeaders = {
"svix-id" : req . headers . get ( "svix-id" ) ! ,
"svix-timestamp" : req . headers . get ( "svix-timestamp" ) ! ,
"svix-signature" : req . headers . get ( "svix-signature" ) ! ,
};
const wh = new Webhook ( process . env . CLERK_WEBHOOK_SECRET ! );
try {
return wh . verify ( payloadString , svixHeaders ) as unknown as WebhookEvent ;
} catch ( error ) {
console . error ( "Error verifying webhook event" , error );
return null ;
}
}
export default http ;
Webhook Events Handled
Creates a new user record in Convex when a user signs up via Clerk. await ctx . runMutation ( internal . users . upsertUserFromClerk , {
data: event . data ,
});
Updates existing user data in Convex when a user modifies their profile. await ctx . runMutation ( internal . users . upsertUserFromClerk , {
data: event . data ,
});
Removes user data from Convex when a user deletes their account. await ctx . runMutation ( internal . users . deleteUser , {
userId: clerkUserId ,
});
User Data Schema
Users are stored in Convex with the following schema:
users : defineTable ({
name: v . string (),
email: v . string (),
userId: v . string (),
phone: v . optional ( v . string ()),
stripeConnectId: v . optional ( v . string ()),
})
. index ( "by_user_id" , [ "userId" ])
. index ( "by_email" , [ "email" ])
Schema Fields
name : User’s display name from Clerk
email : Primary email address
userId : Clerk user ID (used for authentication)
phone : Optional phone number
stripeConnectId : Optional Stripe Connect ID for event organizers
Accessing User Data
In Server Actions
import { auth } from "@clerk/nextjs/server" ;
export async function createEvent () {
const { userId } = await auth ();
if ( ! userId ) throw new Error ( "Not authenticated" );
// Use userId to create event
}
In Client Components
import { useUser } from "@clerk/nextjs" ;
export function ProfileButton () {
const { user } = useUser ();
if ( ! user ) return null ;
return < div > Welcome , {user. firstName } !</ div > ;
}
In Convex Queries
import { query } from "./_generated/server" ;
import { v } from "convex/values" ;
export const getUserTickets = query ({
args: { userId: v . string () },
handler : async ( ctx , { userId }) => {
return await ctx . db
. query ( "tickets" )
. withIndex ( "by_user" , ( q ) => q . eq ( "userId" , userId ))
. collect ();
},
});
Configuring Clerk Webhooks
Go to your Clerk Dashboard
Navigate to Webhooks in the sidebar
Click Add Endpoint
Configure the endpoint:
Endpoint URL : https://your-convex-deployment.convex.cloud/clerk-users-webhook
Subscribe to events :
user.created
user.updated
user.deleted
Copy the Signing Secret and add it to your environment:
CLERK_WEBHOOK_SECRET = whsec_...
Your Convex webhook endpoint must be publicly accessible. Use your production Convex URL, not localhost.
Protected Routes
Make routes require authentication:
src/app/dashboard/page.tsx
import { auth } from "@clerk/nextjs/server" ;
import { redirect } from "next/navigation" ;
export default async function DashboardPage () {
const { userId } = await auth ();
if ( ! userId ) {
redirect ( "/sign-in" );
}
return < div > Protected Dashboard </ div > ;
}
Custom Sign In/Up Pages
Create custom authentication pages:
src/app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs" ;
export default function SignInPage () {
return (
< div className = "flex items-center justify-center min-h-screen" >
< SignIn />
</ div >
);
}
src/app/sign-up/[[...sign-up]]/page.tsx
import { SignUp } from "@clerk/nextjs" ;
export default function SignUpPage () {
return (
< div className = "flex items-center justify-center min-h-screen" >
< SignUp />
</ div >
);
}
Testing
Test Clerk Integration
Start your development server
Navigate to /sign-up
Create a test account
Check Convex dashboard to verify user was created
Test Webhooks Locally
Use Clerk’s webhook testing:
In Clerk Dashboard, go to your webhook endpoint
Click Testing tab
Send test events to verify your handlers work
Next Steps
Stripe Payments Set up payment processing
Convex Database Configure your database